在Erlang中扩展gen_event行为

时间:2012-06-28 02:46:42

标签: erlang gen-event

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

例如,处理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}.

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

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

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

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

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

我不知道下一步该做什么。

2 个答案:

答案 0 :(得分:3)

你准备做什么?我从你提供的例子中无法理解。在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回调的“自定义”实现的模块名称。

E.g。

-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之外,我想不出任何其他的解决方法。

A)

-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})包含到您的每个gen_event模块中,这些模块需要以这种方式进行parse_transformed以添加通用功能(即避免必须自己将其传递给编译器) 。只需确保tmp或包含parse_transform的模块在路径上的dir中加载或编译。

b)看起来像我知道很多工作......

答案 1 :(得分:2)

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

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

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

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

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

等等。