【问题标题】:Extend gen_event behavior in Erlang在 Erlang 中扩展 gen_event 行为
【发布时间】:2012-07-01 02:24:14
【问题描述】:

我正在编写一个事件管理器,它将采用许多不同的事件处理程序。该事件管理器将收到许多不同事件的通知。每个处理程序只处理某些事件,而忽略其余的。每个处理程序还可以根据情况触发某些其他事件。

例如,处理Event1的第一个处理程序

-module (first_handler).
-behavior (gen_event).

...

handle_event(Event1, State) -> {ok, State};
handle_event(_, State) -> {ok, State}.

第二个处理程序来处理Event2

-module (second_handler).
-behavior (gen_event).

...

handle_event(Event2, State) -> 
  gen_event:notify(self(), Event1),
  {ok, State};
handle_event(_, State) -> {ok, State}.

事件触发可以通过在处理程序的handle_event 中调用gen_event:notify(self(), NewEvent) 来完成,但我宁愿将其抽象并导出,以便可以从事件管理器中调用。

由于模式匹配和忽略事件以及触发事件对所有处理程序都是通用的,我是否可以扩展 gen_event 行为以提供这些作为内置函数?

我将从创建自定义行为的默认方式开始:

-module (gen_new_event).
-behaviour (gen_event).

behaviour_info(Type) -> gen_event:behaviour_info(Type).

我不确定下一步该做什么。

【问题讨论】:

    标签: erlang gen-event


    【解决方案1】:

    你到底想做什么?从您提供的示例中我无法理解。在 second_handler 的 handle_event/2 中,Event1 是未绑定的。另外,使用self() 有效吗?那不应该是经理的注册名。不确定handle_event/2 是由管理器执行还是由每个处理程序进程执行(但后者更有意义)。

    通过实现 gen_new_event 模块,您实现的是处理程序(即回调模块),而不是事件管理器。您拥有-behaviour(gen_event) 的事实意味着您要求编译器检查gen_new_event 是否确实实现了gen_event:behaviour_info(callbacks) 列出的所有函数,从而使gen_new_event 成为您可以 符合条件的处理程序> 通过gen_event:add_handler(manager_registered_name, gen_new_event, []) 添加到事件管理器。

    现在,如果你去掉-behaviour (gen_event)gen_new_event就不再需要实现以下功能:

    35> gen_event:behaviour_info(callbacks).   
    [{init,1},
     {handle_event,2},
     {handle_call,2},
     {handle_info,2},
     {terminate,2},
     {code_change,3}]
    

    您可以通过添加更多功能使gen_new_event 成为一种行为(即接口),您需要任何使用-behaviour(gen_new_event) 的模块来实现这些功能:

    -module (gen_new_event).
    -export([behaviour_info/1]).
    
    behaviour_info(callbacks) -> 
        [{some_fun, 2}, {some_other_fun, 3} | gen_event:behaviour_info(callbacks)].
    

    现在,如果在某个模块中,例如-module(example),你添加属性-behaviour(gen_new_event),那么模块example将必须实现所有gen_event回调函数+some_fun/2some_other_fun/3

    我怀疑这就是您要寻找的东西,但您的最后一个示例似乎表明您想要实现一种行为。请注意,您通过实现一个行为所做的只是要求其他模块在使用-behaviour(your_behaviour) 时实现某些功能。

    (另外,如果我理解正确,如果你想扩展gen_event,那么你总是可以简单地复制gen_event.erl 中的代码并扩展它......我猜,但这对你来说真的有必要吗?正在尝试做什么?)。

    编辑

    目标:从 gen_event 实现中提取通用代码。所以例如在每个 gen_events 中都有一个 handle_event/2 子句。

    一种解决方法:您可以使用参数化模块。这个模块将实现 gen_event 行为,但是,只有你的所有 gen_event 回调模块应该具有的常见行为。任何不“常见”的东西都可以委托给模块的参数(您可以将其绑定到包含 gen_event 回调的“自定义”实现的模块名称。

    例如

    -module(abstract_gen_event, [SpecificGenEvent]).
    -behaviour(gen_event).
    -export(... all gen_event functions).
    
    ....
    
    handle_event({info, Info}, State) ->
        %% Do something which you want all your gen_events to do.
    handle_event(Event, State) ->
        %% Ok, now let the particular gen_event take over:
        SpecificGenEvent:handle_event(Event, State).
    
    %% Same sort of thing for other callback functions
    ....
    

    然后您将实现一个或多个 gen_event 模块,并将其插入到 abstract_gen_event 中。假设其中之一是 a_gen_event。

    那么你应该可以做到:

    AGenEvent = abstract_gen_event:new(a_gen_event). %% Note: the function new/x is auto-generated and will have arity according to how many parameters a parameterized module has.
    

    然后,我想您可以将 AGenEvent 传递给 gen_event:add_handler(some_ref, AGenEvent, []) 并且它应该可以工作,但请注意我从未尝试过。

    也许您也可以使用宏来解决这个问题,或者(但这有点矫枉过正)在编译时使用 parse_transform/2 进行一些操作。不过只是一个想法。看看这个参数化的解决方案是如何先行的。

    第二次编辑

    (注意:不确定我是否应该删除本节内容之前的所有内容。请告诉我,或者如果您知道自己在做什么,请直接删除)。

    好的,所以我自己尝试了一下,是的,参数化模块的返回值在将其提供给 gen_event:add_handler/3 的第二个参数时会崩溃......太糟糕了:(

    除了 a) 使用宏 b) 使用 parse_transform/2 之外,我想不出任何其他方法。

    一)

    -module(ge).
    -behaviour(gen_event).
    
    -define(handle_event, 
    handle_event({info, Info}, State) ->
        io:format("Info: ~p~n", [Info]),
        {ok, State}).
    
    ?handle_event;
    handle_event(Event, State) ->
        io:format("got event: ~p~n", [Event]),
        {ok, State}.
    

    因此,基本上,您将在头文件中包含宏定义中定义的通用功能的所有回调函数子句,该头文件包含在使用此通用功能的每个 gen_event 中。然后你?X 在每个使用通用功能的回调函数之前/之后......我知道它不是那么干净,我通常厌倦自己使用宏但是嘿......如果问题真的让你烦恼,那是一种方法去做吧。

    b) 谷歌搜索有关在 Erlang 中使用 parse_transform/2 的一些信息。您可以实现一个 parse_transform,它在您的 gen_event 模块中查找回调函数,这些模块具有回调的特定情况,但没有通用情况(即像 ( {info, Info}, State) 在上面的宏中)。然后,您只需添加构成通用案例的表单。

    我建议做这样的事情(添加导出):

    -module(tmp).
     parse_transform(Forms, Options) ->
         io:format("~p~n", [Forms]),
         Forms.
    
    -module(generic).
    gen(Event, State) ->
        io:format("Event is: ~p~n", [Event]),
        {ok, State}.
    

    现在你可以编译了:

    c(tmp).
    c(generic, {parse_transform, tmp}).
    [{attribute,1,file,{"../src/generic.erl",1}},
     {attribute,4,module,generic},
     {attribute,14,compile,export_all},
     {function,19,gen,2,
           [{clause,19,
                    [{var,19,'Event'},{var,19,'State'}],
                    [],
                    [{call,20,
                           {remote,20,{atom,20,io},{atom,20,format}},
                           [{string,20,"Event is: ~p~n"},
                            {cons,20,{var,20,'Event'},{nil,20}}]},
                     {tuple,21,[{atom,21,ok},{var,21,'State'}]}]}]},
     {eof,28}]
     {ok,generic}
    

    这样您就可以复制粘贴您将要注入的表单。您可以将它们复制到适当的 parse_transform/2 中,而不仅仅是打印,它实际上会遍历您的源代码并将您想要的代码注入到您想要的位置。

    作为旁注,您可以将属性 -compile({parse_transform, tmp}) 包含到您的每个需要以这种方式进行 parse_transformed 以添加通用功能的 gen_event 模块(即避免必须自己将其传递给编译器)。只需确保 tmp 或包含您的 parse_transform 的任何模块都已加载或编译到路径上的目录中。

    b) 我知道的工作似乎很多......

    【讨论】:

    • 非常感谢您花时间帮助我!我知道我可以通过指定需要导出的函数来创建新行为,但我特别希望任何实现我的新行为的模块都可以像事件处理程序一样工作,但已经处理了一些常见的函数子句(从编写我的新行为模块的人。从 gen_event 复制所有代码是没有意义的。我的示例仅列出了两个事件处理程序,因此 handle_event/2 由经理执行。self() 返回事件的 Pid经理,所以它确实有效。
    • 如你所说,可能是管理器正在执行handle_event/2(我从未检查过这一点,并认为也许每个处理程序都执行自己的handle_event/2)。但无论如何,我想我更好地理解你想要什么,而且我想没有固定的方法来解决它。我用一种可能性编辑了我的答案(但我猜没有理由不能使用宏之类的东西)。
    • 你好,我测试过,虽然很有意思,但是参数化模块new/1的返回值不是gen_event:add_handler/3会取的。
    • 是的,这真的很糟糕......虽然我知道参数化模块有点不受欢迎......所以如果你能在没有它们的情况下度过难关,那可能是最好的。当我在答案中建议 macros/parse_transforms 时,添加了一些关于我之前的意思的信息。
    • 我完全忘记了,但至于 b) 选项,您可以使用 Erl AOP 代替:sourceforge.net/projects/erlaop。这将负责为您执行 parse_transform,您只需编写建议以及要在哪里注入它们:)
    【解决方案2】:

    您安装的处理程序已经在您启动然后安装处理程序的事件管理器的上下文中运行。因此,如果他们的句柄事件函数抛出数据,他们已经做了你想要的。

    您不需要扩展事件行为。你要做的是:

     handle_event(Event, State) ->
       generic:handle_event(Event, State).
    

    然后让generic 模块处理通用部分。请注意,您可以提供generic 一种方法来回调此处理程序模块,以便在需要时进行专门的处理程序行为。例如:

       generic:handle_event(fun ?MODULE:callback/2, Event, State)...
    

    等等。

    【讨论】:

    • 您介意详细说明吗?或者给我一个例子? generic 也是我必须添加到 事件管理器 的事件处理程序吗?以及如何将通用部分传递给generic 模块?我想我在这里仍然看不到应用程序。
    猜你喜欢
    • 2010-12-21
    • 2011-06-16
    • 2020-04-14
    • 2011-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-15
    相关资源
    最近更新 更多