如何参数化 gen_server 模块?

发布于 2024-08-02 11:39:27 字数 738 浏览 9 评论 0原文

编辑:

我不打算使用参数作为构建 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 在您的实例上抛出消息。如果我能告诉实例它们的名字,这个问题就可以得到缓解。

EDIT:

I'm not looking to use parameters as a general purpose way to construct Erlang programs--I'm still learning the traditional design principles. I'm also not looking to emulate OOP. My only point here is to make my gen_server calls consistent across server instances. This seems more like fixing a broken abstraction to me. I can imagine a world where the language or OTP made it convenient to use any gen_server instance's api, and that's a world I want to live in.

Thanks to Zed for showing that my primary objective is possible.


Can anyone figure out a way to use parameterized modules on gen_servers? In the following example, let's assume that test_child is a gen_server with one parameter. When I try to start it, all I get is:

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

Ultimately, I'm trying to figure out a way to use multiple named instances of a gen_server. As far as I can tell, as soon as you start doing that, you can't use your pretty API anymore and have to throw messages at your instances with gen_server:call and gen_server:cast. If I could tell instances their names, this problem could be alleviated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

天生の放荡 2024-08-09 11:39:27

我只想说两件事:

  • archaelus 的解释是正确的。正如他所说,他展示的最后一种方法是推荐的方法,并且达到了您的期望。

  • 永远,永远,永远永远使用您正在尝试的表单!它是旧时代遗留下来的,从来没有达到您的预期目的,现在已被强烈弃用。

I just want to say two things:

  • archaelus explains it correctly. As he says the final way he shows is the recommended way of doing it and does what you expect.

  • never, NEVER, NEVER, NEVER use the form you were trying! It is a left over from the old days which never meant what you intended and is strongly deprecated now.

王权女流氓 2024-08-09 11:39:27

这个答案有两个部分。首先,在您非常精通 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 与参数化模块兼容:

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

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

Param1Param2 将在所有 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 及其接口/构造函数模块)。需要注意的是,这种风格使得 grep 代码库以供 some_module:start_link 使用变得更加困难。


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

-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函数时,您可以返回一个新的状态值。没有相应的方法可以将新的参数化模块实例返回到 gen_server 代码。

There are two parts to this answer. The first is that you probably don't want to use paramatized modules until you're quite proficient with Erlang. All they give you is a different way to pass arguments around.

-module(test_module, [Param1]).

some_method() -> Param1.

is equivalent to

-module(test_non_paramatized_module).

some_method(Param1) -> Param1.

The former doesn't buy you much at all, and very little existing Erlang code uses that style.

It's more usual to pass the name argument (assuming you're creating a number of similar gen_servers registered under different names) to the start_link function.

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

The second part to the answer is that gen_server is compatible with paramatized modules:

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

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

Param1 and Param2 will then be available in all the gen_server callback functions.

As Zed mentions, as start_link belongs to a paramatized module, you would need to do the following in order to call it:

Instance = some_module:new(Param1, Param2),
Instance:start_link().

I find this to be a particularly ugly style - the code that calls some_module:new/n must know the number and order of module parameters. The code that calls some_module:new/n also cannot live in some_module itself. This in turn makes a hot upgrade more difficult if the number or order of the module parameters change. You would have to coordinate loading two modules instead of one (some_module and its interface/constructor module) even if you could find a way to upgrade running some_module code. On a minor note, this style makes it somewhat more difficult to grep the codebase for some_module:start_link uses.


The recommended way to pass parameters to gen_servers is explicitly via gen_server:start_link/3,4 function arguments and store them in the state value you return from the ?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}}.

Using this style means that you won't be caught by the various parts of OTP that don't yet fully support paramatized modules (a new and still experimental feature). Also, the state value can be changed while the gen_server instance is running, but module parameters cannot.

This style also supports hot upgrade via the code change mechanism. When the code_change/3 function is called, you can return a new state value. There is no corresponding way to return a new paramatized module instance to the gen_server code.

哆啦不做梦 2024-08-09 11:39:27

我认为您不应该以这种方式使用此功能。看起来您正在为您的 gen_servers 寻找类似 OO 的接口。为此,您正在使用本地注册的名称 - 这会在您的程序中添加大量共享状态,这是坏事。只有关键和中央服务器才应该使用 register BIF 进行注册 - 让所有其他服务器都未命名并由某种管理器在它们之上进行管理(可能应该在下注册)一些名字)。

I think you shouldn't use this feature this way. Looks like you are going after a OO-like interface to your gen_servers. You are using locally-registered names for this purpose - this add a lot of shared state into your program, which is The Bad Thing. Only crucial and central servers should be registered with register BIF - let all the others be unnamed and managed by some kind of manager on top of them (which should probably be registered under some name).

仄言 2024-08-09 11:39:27
-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
-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}.

This module is working fine for me:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文