Доброго времени суток. В данном посте будет очень кратко рассказано о вынесенных в заголовках типах модулей в erlang. Будет пара примеров того6 как это можно использовать в личных целях.
Behavior
Единственный вид модулей, про которые я нашёл информацию в стандартной документации и книгах (может быть, плохо искал). Идея очень проста, у нас есть некоторая типовая задача, которая должна адаптироваться под конкретные случаи. В частности любое конкретное решение складывается из 2-ух модулей, behavior модуль, который реализует типовое поведение, и callback модуль, которые реализует необходимое поведение. В качестве примера можно привеси gen_server. Но из-за объёмности, лучше взять более локальную задачу - генератор последовательностей.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 -module(sequence).
-export([behaviour_info/1 , start/1 , get/1 ]).
behaviour_info(callbacks) -> % returned list with all needed callbacks [{calc,1}, {from,0}]; behaviour_info(_Other) -> undefined.
start(Module) -> Pid = spawn(fun() -> loop(Module:from(), Module) end), register(Module, Pid).
get(Module) -> Module ! {get, self()}, receive X -> X end.
loop(X, Module) -> receive {get, Pid} -> Y = Module:calc(X), Pid ! Y, loop(Y, Module) end.
Приведённый выше код является behavior модулем и он не может быть использован сам по себе, и должен работать в связке с другим модулем. Функция behavior_info/1 должна возвращать список требуемых callback функций. Это позволит при компиляции сборки проверить, были ли определены все необходимые функции. Вот пример модуля, который определяет необходимые callback функции для генерации списка натуральных чисел (будем считать ноль не натуральным числом) и интерфейс для работы с ним.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 -module(natural).
-behaviour(sequence).
% export API -export([start/0 , next/0]).
% export callbacks -export([from/0 , calc/1]).
% API start() -> sequence:start(?MODULE).
next() -> sequence:get(?MODULE).
% Callbacks from() -> 0.
calc(X) -> X + 1.
Parameterized module
Параметризованными модулями являются модуль, которые должен быть связан со значением перед использованием. Одним из вариантов его применение – симуляция ООП. Пример такого использования можно найти в исходном коде MochiWeb (src/mochiweb_request.erl). Заголовок подобного модуля должен иметь следующий вид:
6 -module(mochiweb_request, [Socket, Method, RawPath, Version, Headers]).
В теле данного модуля появляются глобальные «переменные», которые могут использованы точно также, как и обычные. Перед использованием подобного модуля необходимо сперва создать эффективный модуль (терминология, видимо, моя), в котором будут определены данные переменные. Эффективный метод создаётся следующим образом:Mod = mochiweb_request:new(Socket,
Method,
Uri,
Version,
mochiweb_headers:make(Headers))
В качестве результата в Mod будет сохранён следующий кортеж: {mochiweb_request, Socket, Method, Uri, Version, Headers}, а вызовы методов должны осуществляться следующим образом: Mod:get(body_length) Такой метод позволяет скрыть в модуль необходимые данные, что позволяет не передавать их каждый раз при вызове функций, а следовательно упростить код.
Так же можно использовать для создания модулей параметризуемых другими модулями. Именно таким методом мне удалось сделать костыль в одной задаче (до написания этого поста я его костылём не считал).Extends module
Данный тип модулей позволяет реализовать некоторый аналог наследования. В частности, если вернуться к примеру с генератором последовательности. Предположим у нас возникает необходимость реализовать не локальный генератор последовательности, а глобальный, то можно определить следующий модуль.
1 2 3 4 5 6 7 8 -module(global_sequence).
-extends(sequence). -export([start/1]).
start(Module) -> {ok, Pid} = spawn(fun() -> Module:loop(Module:from()) end), global:register_name(Module, Pid).
Теперь у нас есть новый behavior модуль, который можно использовать для создания глобальных последовательностей, для чего необходимо изменить только одну строку в callback модуле.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-module(sequence).
-export([behaviour_info/1
, start/1
, get/1
]).
behaviour_info(callbacks) -> % returned list with all needed callbacks
[{calc,1},
{from,0}];
behaviour_info(_Other) ->
undefined.
start(Module) ->
Pid = spawn(fun() -> loop(Module:from(), Module) end),
register(Module, Pid).
get(Module) ->
Module ! {get, self()},
receive X -> X end.
loop(X, Module) ->
receive
{get, Pid} -> Y = Module:calc(X),
Pid ! Y,
loop(Y, Module)
end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-module(natural).
-behaviour(sequence).
% export API
-export([start/0
, next/0]).
% export callbacks
-export([from/0
, calc/1]).
% API
start() ->
sequence:start(?MODULE).
next() ->
sequence:get(?MODULE).
% Callbacks
from() -> 0.
calc(X) -> X + 1.
6
-module(mochiweb_request, [Socket, Method, RawPath, Version, Headers]).
Так же можно использовать для создания модулей параметризуемых другими модулями. Именно таким методом мне удалось сделать костыль в одной задаче (до написания этого поста я его костылём не считал).
1
2
3
4
5
6
7
8
-module(global_sequence).
-extends(sequence).
-export([start/1]).
start(Module) ->
{ok, Pid} = spawn(fun() -> Module:loop(Module:from()) end),
global:register_name(Module, Pid).