Расширить поведение 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 на пути.
б) кажется, много работы я знаю...