编辑:
我不打算使用参数作为构建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
最终,我试图想出一种方法来使用gen_server的多个命名实例。据我所知,一旦你开始这样做,你就不能再使用漂亮的API了,必须使用gen_server:call和gen_server:cast在你的实例中抛出消息。如果我可以告诉实例他们的名字,这个问题可以缓解。
答案 0 :(得分:10)
这个答案有两个部分。首先,你可能不想使用paramatized模块,直到你非常精通Erlang。他们给你的只是一种传递论据的不同方式。
-module(test_module, [Param1]).
some_method() -> Param1.
相当于
-module(test_non_paramatized_module).
some_method(Param1) -> Param1.
前者根本不会给你带来太多收益,现有的Erlang代码很少使用这种风格。
通常将name参数(假设您正在创建一些以不同名称注册的类似gen_servers)传递给start_link函数。
start_link(Name) -> gen_server:start_link({local, Name}, ?MODULE, [Name], []).
答案的第二部分是gen_server与paramatized模块兼容:
-module(some_module, [Param1, Param2]).
start_link() ->
PModule = ?MODULE:new(Param1, Param2),
gen_server:start_link(PModule, [], []).
然后 Param1
和Param2
将在所有gen_server回调函数中可用。
正如Zed所提到的那样,由于start_link
属于一个参数化模块,你需要做以下几点才能调用它:
Instance = some_module:new(Param1, Param2),
Instance:start_link().
我发现这是一种特别丑陋的风格 - 调用some_module:new/n
的代码必须知道模块参数的数量和顺序。调用some_module:new/n
的代码也无法生效some_module
。如果模块参数的数量或顺序发生变化,这又会使热升级变得更加困难。即使您可以找到升级运行some_module
代码的方法,也必须协调加载两个模块而不是一个(some_module
及其接口/构造函数模块)。简而言之,这种风格使得some_module:start_link
使用的代码库变得更加困难。
将参数传递给gen_servers
的推荐方法是通过gen_server:start_link/3,4
函数参数显式地将它们存储在从?MODULE:init/1
callack返回的状态值中。
-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}}.
使用这种风格意味着你不会被OTP的各个部分所捕获,这些部分还没有完全支持参数化模块(一种新的,仍然是实验性的功能)。此外,可以在gen_server实例运行时更改状态值,但模块参数不能。
此样式还支持通过代码更改机制进行热升级。调用code_change/3
函数时,可以返回新的状态值。没有相应的方法可以将新的paramatized模块实例返回到gen_server
代码。
答案 1 :(得分:10)
我只想说两件事:
archaelus正确解释。正如他所说的那样,他展示的最终方式是推荐的做法并做到了你所期望的。
从来没有,从不,从不使用您正在尝试的表单!它是过去遗留下来的,从未意味着你想要的东西,现在已被强烈弃用。
答案 2 :(得分:3)
我认为你不应该这样使用这个功能。看起来你正在寻找与gen_servers类似的OO接口。您为此目的使用了本地注册的名称 - 这会在您的程序中添加很多共享状态,这是糟糕的事情。只有关键和中央服务器应该在register
BIF注册 - 让所有其他服务器都由未经命名并由某种经理管理(可能应该以某个名称注册)
答案 3 :(得分:-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}.
这个模块对我来说很好用:
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