Расширить поведение gen_event в Erlang

Я пишу менеджер событий, который будет принимать много разных обработчиков событий. Этот менеджер событий будет уведомлен с большим количеством различных событий. Каждый обработчик обрабатывает только определенные события, а остальные игнорирует. Каждый обработчик также может инициировать некоторые другие события в зависимости от ситуации.

Например, первый обработчик для обработки 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 ответа

Решение

Ваши установленные обработчики уже работают в контексте менеджера событий, который вы запускаете, а затем устанавливаете обработчики. Так что если их функция handle-event выбрасывает данные, они уже делают то, что вы хотите.

Вам не нужно расширять поведение события. Что вы делаете, это:

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

а затем пусть generic Модуль обрабатывает общие части. Обратите внимание, что вы могли бы поставить generic способ обратного вызова к этому модулю обработчика для специализированного поведения обработчика, если вам это нужно. Например:

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

и так далее.

Что вы пытаетесь сделать именно? Я не мог понять из приведенных вами примеров. Во второй раз 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/2 а также some_other_fun/3,

Я сомневаюсь, что это то, что вы искали, но ваш последний пример, казалось, предполагал, что вы хотели реализовать поведение. Обратите внимание, что все, что вы делаете, реализуя поведение, - это требует, чтобы другие модули реализовывали определенные функции, если они используют -behaviour(your_behaviour),

(Также, если я вас правильно понял, если вы хотите продлить gen_event тогда вы всегда можете просто скопировать код в gen_event.erl и расширить его... я думаю, но действительно ли это необходимо для того, что вы пытаетесь сделать?).

редактировать

Цель: извлечь общий код из реализаций gen_event. Так, например, есть предложение handle_event/2, которое вы хотите в каждом из ваших gen_events.

Один из способов сделать это: вы можете использовать параметризованный модуль. Этот модуль будет реализовывать поведение 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. Просто мысль, хотя. Посмотрите, как это параметризованное решение идет первым.

2-й править

(Примечание: не уверен, должен ли я удалять все до того, что находится в этом разделе. Пожалуйста, дайте мне знать или просто удалите это, если вы знаете, что делаете).

Итак, я попробовал это сам, и да, возвращаемое значение параметризованного модуля потерпит крах при передаче его в gen_event: второй аргумент add_handler/3... очень плохо:(

Я не могу думать ни о каком другом способе сделать это, кроме а) использования макросов б) использования 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 до / после каждой функции обратного вызова, которая использует общую функциональность... Я знаю, что она не такая уж чистая, и я, как правило, устаю от использования макросов, но эй... если проблема действительно раздражает вас, это один из способов пойти на это.

б) Поищите в Google информацию об использовании parse_transform / 2 в Erlang. Вы могли бы реализовать 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 на пути.

б) кажется, много работы я знаю...

Другие вопросы по тегам