I have a map that is managed with a module with a gen_server behavior, where I'm able to add, remove and update key->values.
I also have a main module with some routines and subroutines where I act depending on the key->values that I have in the map. My problem is that I try to modify the map during the execution of my module, but I didn't get any answer.
This is an example of the structure of my main module:
-export([
go/0,
add_belief/1
]).
go()->
bs:start_link(),
collect_bottles(0).
collect_bottles(Total) ->
case {bs:is_belief(holding), bs:is_belief(over_drop)} of
{true, true} -> drop_and_leave();
{true,false} -> get_to_drop();
{false, _} -> get_bottle()
end.
get_bottle()->
io:format("Getting bottle.~n"),
case {bs:get_belief(see)} of
{true} -> collect_bottles(bs:get_belief(collected));
{false} ->move(),
get_bottle()
end.
move(Dist)->
io:format("Start moving...~n"),
timer:sleep(5000).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%% GOD FUNCTIONS %%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
add_belief(Belief)->
bs:add_belief(Belief).
The code of bs:add_belief(Belief)
is:
add_belief(Belief)->
gen_server:cast(?MODULE,{add,Belief}).
And in the gen_server
function:
handle_cast({add,{Key,Value}},State)->
io:format("Belief added: ~p.~n",[{Key,Value}]),
{noreply, maps:put(Key,Value,State)};
When I run my script, I get:
tr:go().
Getting bottle.
Start moving...
Getting bottle.
Start moving...
And I can't not use another function (I would like to use add_belief({see,bottle})
to get out of the loop.
Can I modify a map that is managed with a gen_server with external functions?
Yes, here is proof:
$ erl
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V8.2 (abort with ^G)
1> c(tr).
{ok,tr}
2> c(bs).
{ok,bs}
3> c(env).
{ok,env}
4> tr:test().
tr:get_bottle(): getting bottle
<0.74.0>
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
5> env:get_state().
env: get_state(): 10
ok
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
6> bs:get_state().
bs:get_state(): #{}
ok
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
7> bs:add_belief({holding, []}).
Adding belief: {holding,[]}
ok
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
8> bs:get_state().
bs:get_state(): #{holding=>[]}
ok
tr:get_bottle(): getting bottle
9>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
$
If instead I change my bs:erl
file to call gen_server:start_link()
like this:
start_link() ->
gen_server:start_link(
%%{local, ?MODULE},
?MODULE, [], []
).
then this is what happens:
1> c(tr).
{ok,tr}
2> c(bs).
{ok,bs}
3> c(env).
{ok,env}
4> tr:test().
tr:get_bottle(): getting bottle
<0.74.0>
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
5> bs:add_belief({holding, []}).
ok
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
6> bs:get_state().
** exception exit: {noproc,{gen_server,call,[bs,get_state]}}
in function gen_server:call/2 (gen_server.erl, line 204)
in call from bs:get_state/0 (bs.erl, line 40)
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
tr:get_bottle(): getting bottle
From the gen_server docs about gen_server:start_link()
:
The first argument, {local, ch3}, specifies the name. The gen_server is then locally registered as ch3.
If the name is omitted, the gen_server is not registered. Instead its pid must be used. The name can also be given as {global, Name}, in which case the gen_server is registered using global:register_name/2.
If you don't specify {local, ?MODULE}
as an argument to gen_server:start_link()
, then you have to call gen_server:call()
like this:
gen_server:call(ServerPid, Request)
To get the ServerPid, you need to do something like this:
start_link() ->
{ok, ServerPid} = gen_server:start_link(
%%{local, ?MODULE},
?MODULE, [], []
),
ServerPid.
From the last shell session above, it looks like if you don't specify {local, ServerName}
--and thus you do not register the server name--and you call gen_server:cast(?MODULE, ...)
it won't cause an error, but if you call gen_server:call(?MODULE...)
you will get an error. To me it seems like it would be handy if you got an error in both cases when the server wasn't registered--the return value of ok
for the cast is pretty misleading.
tr.erl
:
-module(tr).
%%-compile(export_all).
-export([go/0, test/0]).
go() ->
bs:start_link(),
env:start_link(),
collect_bottles(0).
collect_bottles(_Total) ->
get_bottle().
get_bottle() ->
io:format("tr:get_bottle(): getting bottle~n"),
timer:sleep(3000),
get_bottle().
test() ->
spawn(tr, go, []).
bs.erl
:
-module(bs).
%%-compile(export_all).
-export([init/1, handle_call/3, handle_cast/2]).
-export([handle_info/2, terminate/2, code_change/3]).
-export([start_link/0, add_belief/1, get_state/0, stop/0]).
%%Internal server functions:
init([]) ->
{ok, #{}}. %%<******** INITIALIZE STATE WITH AN EMPTY MAP
handle_cast({add, {Key, Val}=Belief}, State) ->
io:format("Adding belief: ~w~n", [Belief]),
{ noreply, maps:put(Key, Val, State) }.
handle_call(get_state, _From, State) ->
{reply, State, State}.
% -----
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%External interface:
start_link() ->
gen_server:start_link(
{local, ?MODULE},
?MODULE, [], []
).
add_belief(Belief) -> %%<******* EXTERNAL FUNCTION THAT MODIFIES A MAP
gen_server:cast(?MODULE, {add, Belief}).
get_state() ->
State = gen_server:call(?MODULE, get_state),
io:format("bs:get_state(): ~w~n", [State]).
stop() ->
gen_server:stop(?MODULE).
env.erl
:
-module(env).
%%-compile(export_all).
-export([init/1, handle_call/3, handle_cast/2]).
-export([handle_info/2, terminate/2, code_change/3]).
-export([start_link/0, get_state/0, stop/0]).
%%Internal server functions:
init([]) ->
{ok, 10}. %%<***** INITIALIZE STATE WITH 10
handle_call(get_state, _From, State) ->
{reply, State, State}.
%% ------
handle_cast(_Request, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%External interface:
start_link() ->
gen_server:start_link(
{local, ?MODULE},
?MODULE, [], []
).
get_state() ->
State = gen_server:call(?MODULE, get_state),
io:format("env: get_state(): ~w~n", [State]).
stop() ->
gen_server:stop(?MODULE).