你如何参数化gen​​_server模块?

mwt*_*mwt 5 erlang

编辑:

我不打算使用参数作为构建Erlang程序的通用方法 - 我仍在学习传统的设计原则.我也不想模仿OOP.我唯一的观点是让我的gen_server调用在服务器实例之间保持一致.这似乎更像是修复了一个破碎的抽象给我.我可以想象一个语言或OTP使得使用任何gen_server实例的api变得方便的世界,这是我想要生活的世界.

感谢Zed表明我的主要目标是可行的.


谁能想出一种在gen_servers上使用参数化模块的方法?在下面的示例中,假设test_child是具有一个参数的gen_server.当我尝试启动它时,我得到的是:

42> {test_child, "hello"}:start_link().
** exception exit: undef
     in function  test_child:init/1
        called as test_child:init([])
     in call from gen_server:init_it/6
     in call from proc_lib:init_p_do_apply/3
Run Code Online (Sandbox Code Playgroud)

最后,我试图想出一种方法来使用gen_server的多个命名实例.据我所知,一旦你开始这样做,就不能再使用漂亮的API了,必须使用gen_server:call和gen_server:cast在你的实例中抛出消息.如果我可以告诉实例他们的名字,这个问题可以缓解.

arc*_*lus 10

这个答案分为两部分.首先,你可能不想使用paramatized模块,直到你非常精通Erlang.他们给你的只是一种传递论据的不同方式.

-module(test_module, [Param1]).

some_method() -> Param1.
Run Code Online (Sandbox Code Playgroud)

相当于

-module(test_non_paramatized_module).

some_method(Param1) -> Param1.
Run Code Online (Sandbox Code Playgroud)

前者根本不会给你带来很多东西,现有的Erlang代码很少使用这种风格.

通常将name参数(假设您正在创建一些以不同名称注册的类似gen_servers)传递给start_link函数.

start_link(Name) -> gen_server:start_link({local, Name}, ?MODULE, [Name], []).
Run Code Online (Sandbox Code Playgroud)

答案的第二部分是gen_server与paramatized模块兼容:

-module(some_module, [Param1, Param2]).

start_link() -> 
  PModule = ?MODULE:new(Param1, Param2),
  gen_server:start_link(PModule, [], []).
Run Code Online (Sandbox Code Playgroud)

Param1Param2随后将在所有的gen_server回调函数可用.

正如Zed所提到的,start_link属于一个paramatized模块,你需要执行以下操作来调用它:

Instance = some_module:new(Param1, Param2),
Instance:start_link().
Run Code Online (Sandbox Code Playgroud)

我发现这是一种特别丑陋的风格 - 调用的代码some_module:new/n必须知道模块参数的数量和顺序.调用的代码some_module:new/nsome_module不能生存.如果模块参数的数量或顺序发生变化,这又会使热升级变得更加困难.some_module即使您可以找到升级运行some_module代码的方法,您也必须协调加载两个模块而不是一个(及其接口/构造函数模块).简而言之,这种风格使得some_module:start_link使用代码库变得更加困难.


传递参数的推荐方法gen_servers是通过gen_server:start_link/3,4函数参数显式地将它们存储在从?MODULE:init/1callack 返回的状态值中.

-module(good_style).

-record(state, {param1, param2}).

start_link(Param1, Param2) ->
  gen_server:start_link(?MODULE, [Param1, Param2], []).

init([Param1, Param2]) ->
  {ok, #state{param1=Param1,param2=Param2}}.
Run Code Online (Sandbox Code Playgroud)

使用这种风格意味着您不会被OTP的各个部分所捕获,这些部分还没有完全支持参数化模块(一种新的仍然是实验性的功能).此外,可以在gen_server实例运行时更改状态值,但模块参数不能.

此样式还支持通过代码更改机制进行热升级.code_change/3调用该函数时,可以返回新的状态值.没有相应的方法可以将新的paramatized模块实例返回给gen_server代码.

  • 我想你可以创建一个接口模块(已经参数化),通过gen_server:call/2与非参数化的gen_server进行通信.这样可以避免gen_server的升级问题,并且意味着您以不同方式传递服务器引用(作为一个paramatized模块而不是函数参数).您仍然会遇到协调双模块升级问题,我不能推荐这个好风格. (2认同)

rvi*_*ing 10

我只想说两件事:

  • archaelus正确地解释了它.正如他所说的那样,他展示的最终方式是推荐的做法并做到了你所期望的.

  • 从来没有,永远,永远,永远不要使用你正在尝试的形式!它是过去遗留下来的,从未意味着你的意图,现在已被强烈弃用.


Zed*_*Zed -4

-module(zed, [Name]).
-behavior(gen_server).

-export([start_link/0, init/1, handle_cast/2]).
-export([increment/0]).

increment() ->
    gen_server:cast(Name, increment).

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

init([]) ->
    {ok, 0}.

handle_cast(increment, Counter) ->
    NewCounter = Counter + 1,
    io:format("~p~n", [NewCounter]),
    {noreply, NewCounter}.
Run Code Online (Sandbox Code Playgroud)

这个模块对我来说工作得很好:

Eshell V5.7.2  (abort with ^G)
1> S1 = zed:new(s1).
{zed,s1}
2> S1:start_link().
{ok,<0.36.0>}
3> S1:increment().
1
ok
4> S1:increment().
2
ok
Run Code Online (Sandbox Code Playgroud)