Rodrigue
Rodrigue

Reputation: 3687

How to save state in an Erlang process?

I am learning Erlang and trying to figure out how I can, and should, save state inside a process.

For example, I am trying to write a program that given a list of numbers in a file, tells me whether a number appears in that file. My approach is to uses two processes

This approach is obviously naive since the file is read for every request. However, I am quite new at Erlang and it is unclear to me what would be the preferred way of keeping state between different requests.

Should I be using the process dictionary? Is there a different mechanism I am not aware of for that sort of process state?

Update

The most obvious solution, as suggested by user601836, is to pass the set of numbers as a param to is_member_loop instead of the filename. It seems to be a common idiom in Erlang and there is a good example in the fantastic online book Learn you some Erlang.

I think, however, that the question still holds for more complex state that I'd want to preserve in my process.

Upvotes: 7

Views: 3638

Answers (2)

user601836
user601836

Reputation: 3235

Simple solution, you can pass to your function is_member_loop(Data_file) the list of numbers rather then the file name.

The best solution when you deal with a state consists in using a gen_server. To learn more you should take a look at records and gen_server behaviour (this may also be useful).

In practice:

1) start with a module (yourmodule.erl) based on gen_server behaviour 2) read your file in the init function of the gen_server and pass it as state field:

init([]) ->
    Numbers = read_numbers(Data_file),
{ok, #state{numbers=Numbers}}.

3) write a function which will be used to trigger a call to the gen_server

check_number(Number) ->
    gen_server:call(?MODULE, {check_number, Number}).

4) write the code in order to handle messages triggered from your function

handle_call({check_number, Number}, _From, #state{numbers=Numbers} = State) ->
    Reply = lists:member(Number, Numbers)},
{reply, Reply, State};

handle_call(_Request, _From, State) ->
    Reply = ok,
{reply, Reply, State}.

5) export from yourmodule.erl function check_number

-export([check_number/1]).

Two things to be explained about point 4:

a) we extract values inside the record State using pattern matching

b) As you may see I left the generic handle call, otherwise your gen_server will fail due to wrong pattern matching whenever a message different from {check_number, Number} is received

Note: if you are new to erlang, don't use process dictionary

Upvotes: 10

Inaimathi
Inaimathi

Reputation: 14065

Not sure how idiomatic this is, since I'm not exactly an Erlang pro yet, but I'd handle this by using ETS. Basically,

read_numbers_to_ets(DataFile) ->
    Table = ets:new(numbers, [ordered_set]),
    insert_numbers(Table, DataFile),
    Table.

insert_numbers(Table, DataFile) ->
    case read_next_number(DataFile) of
    eof -> ok;
    Num -> ets:insert(numbers, {Num})
    end.

you could then define your is_member as

is_member(TableId, Number) ->
    case ets:match(TableId, {Number}) of
    [] -> false; %% no match from ets
    [[]] -> true %% ets found the number you're looking for in that table
    end.

Instead of taking a Data_file, your is_member_loop would take the id of the table to do a lookup on.

Upvotes: 2

Related Questions