Как создать и использовать пользовательское поведение Erlang?

Попытка определить настраиваемое поведение в Erlang, я не могу найти способ применить функцию обратного вызова внутри модуля определения поведения. Компилятор утверждает, что функция обратного вызова undefined.

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

В приведенном ниже примере определяется функция обратного вызова fn. Затем эта функция используется в add_one. Фактически fn фактически контролируется модулем Erlang, реализующим это поведение.

-module( mybeh ).
-callback fn( A::number() ) -> B::number().
-export( [add_one/1] ).
add_one( A ) ->
 1+fn( A ).

Но когда я пытаюсь скомпилировать файл mybeh.erl, я получаю следующее сообщение об ошибке:

$ erlc mybeh.erl
mybeh.erl:8: function fn/1 undefined

Примеры кода, которые я нашел на erlangcentral.org, learnyousomeerlang.com или metajack.im были слишком упрощенными, чтобы охватить этот случай. Также мне не повезло, что я прошел через хорошо известные проекты Erlang на Github (возможно, он попытался усердно).

1 ответ

Вы очень близки. Это на самом деле проще, чем вы пробовали:

-module(my_behavior).
-callback fn(A :: term()) -> B :: term().

Компилятор может понять это полностью, как есть.

Так просто, это своего рода разочарование.

ИЗМЕНИТЬ

"Прохладный рассказ, как его использовать?"

Ничто не говорит как рабочий пример:

Здесь у нас есть абстрактный сервис. Предполагается, что он реагирует на очень узкий спектр сообщений и высмеивает все остальное. Его особый элемент состоит в том, что он принимает в качестве аргумента startup имя модуля, который определяет какой-то особый аспект его поведения - и это модуль обратного вызова.

-module(my_abstract).
-export([start/1]).
start(CallbackMod)-> 
 spawn(fun() -> loop(CallbackMod) end).
loop(CBM) ->
 receive
 {Sender, {do_it, A}} ->
 Sender ! CBM:fn(A),
 loop(CBM);
 stop ->
 io:format("~p (~p): Farewell!~n",
 [self(), ?MODULE]);
 Message ->
 io:format("~p (~p): Received silliness: ~tp~n",
 [self(), ?MODULE, Message]),
 loop(CBM)
 end.

Итак, здесь мы определяем действительно простой модуль обратного вызова в соответствии с поведением, определенным как 'my_behavior' выше:

-module(my_callbacks).
-behavior(my_behavior).
-export([fn/1]).
fn(A) -> A + 1.

Здесь он находится в действии!

1> c(my_behavior).
{ok,my_behavior}
2> c(my_abstract).
{ok,my_abstract}
3> c(my_callbacks).
{ok,my_callbacks}
4> Service = my_abstract:start(my_callbacks).
<0.50.0>
5> Service ! {self(), {do_it, 5}}.
{<0.33.0>,{do_it,5}}
6> flush().
Shell got 6
ok
7> Service ! {self(), {do_it, 41}}.
{<0.33.0>,{do_it,41}}
8> flush(). 
Shell got 42
ok
9> Service ! stop.
<0.50.0> (my_abstract): Farewell!
stop

Итак, что хорошего в определении поведения? Это на самом деле ничего не делало! Хорошо, что это за все эти объявления иерархии типов Dialyzer? Они тоже ничего не делают. Но они помогают вам автоматически проверять вашу работу, чтобы убедиться, что вы не испытаете некоторую захватывающую неудачу во время выполнения, но ни Dialyzer, ни определения поведения не заставят вас что-либо сделать: они просто предупреждают нас о нашей (вероятной) надвигающейся гибели:

-module(my_other_callbacks).
-behavior(my_behavior).
-export([haha_wtf/1]).
haha_wtf(A) -> A - 1.

И когда мы построим это, получим:

10> c(my_other_callbacks).
my_other_callbacks.erl:2: Warning: undefined callback function fn/1 (behaviour 'my_behavior')
{ok,my_other_callbacks}

Обратите внимание, что этот модуль был фактически скомпилирован и по-прежнему доступен для использования (но не с помощью нашей абстрактной службы, которая ожидает найти fn/1, определенную во всем, что называется my_behavior):

11> my_other_callbacks:haha_wtf(5).
4

Надеюсь, это небольшое прохождение проливает свет на путь.

licensed under cc by-sa 3.0 with attribution.