我可以修改由带有外部函数的 gen_server 管理的地图吗?

Can I modify a map that is managed with a gen_server with external functions?

我有一个由具有 gen_server 行为的模块管理的地图,我可以在其中添加、删除和更新键->值。

我还有一个包含一些例程和子例程的主模块,我在其中根据映射中的键-> 值进行操作。我的问题是我试图在我的模块执行期间修改地图,但我没有得到任何答案。

这是我的主要模块的结构示例:

-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).

bs:add_belief(Belief)的代码是:

add_belief(Belief)->
    gen_server:cast(?MODULE,{add,Belief}).

并且在 gen_server 函数中:

handle_cast({add,{Key,Value}},State)->
    io:format("Belief added: ~p.~n",[{Key,Value}]),
    {noreply, maps:put(Key,Value,State)};

当我 运行 我的脚本时,我得到:

tr:go().
Getting bottle.
Start moving...
Getting bottle.
Start moving...

而且我不能不使用另一个函数(我想使用 add_belief({see,bottle}) 来跳出循环。

Can I modify a map that is managed with a gen_server with external functions?

是的,这是证明:

$ 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
$ 

如果我改为更改我的 bs:erl 文件以调用 gen_server:start_link(),如下所示:

start_link() ->
    gen_server:start_link(
      %%{local, ?MODULE},
      ?MODULE, [], []
    ).

然后是这样:

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

来自gen_server docs 关于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.

如果您没有指定 {local, ?MODULE} 作为 gen_server:start_link() 的参数,那么您必须像这样调用 gen_server:call()

 gen_server:call(ServerPid, Request)

要获取 ServerPid,您需要执行以下操作:

start_link() ->
    {ok, ServerPid} = gen_server:start_link(
                          %%{local, ?MODULE},
                          ?MODULE, [], []
                      ),
    ServerPid.

从上面的最后一个 shell 会话来看,如果您不指定 {local, ServerName}——因此您不会注册服务器名称——并且您调用 gen_server:cast(?MODULE, ...) 它不会导致错误,但是如果你调用 gen_server:call(?MODULE...) 你会得到一个错误。对我来说,如果在服务器未注册的情况下在这两种情况下都出现错误,这似乎很方便——演员表的 ok 的 return 值非常具有误导性。


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).