Поведение

Поведение — произвольное требование к текущему модулю, заключающееся в том, что в модуле должны быть определены некоторые функции определённой арности. То же: behaviour.

Это требование включается такой строкой в заголовке файла модуля:

-behaviour(ИМЯ_МОДУЛЯ_В_КОТОРОМ_ОПРЕДЕЛЕНО_ПОВЕДЕНИЕ). 

Например:

-behaviour(poved).

Когда компилятор наткнётся на эту инструкцию, он полезет искать в своих путях уже скомпилированный модуль с именем poved. Отсюда два вывода:

Очень может быть, что текущий каталог, в котором идёт разработка, отсутствует в путях Эрланга. Поэтому при компиляции может понадобиться добавить флаг -pa, чтобы компилятор нашёл поведение. Пример (мы тут компилируем файл, который создадим чуть позже):

$ erlc -pa . homo1.erl

Точка после -pa означает текущий каталог. Вместо неё, конечно, можно поставить какой-нибудь другой путь, по которому находится модуль-поведение.

Итак, раз нам надо сначала создать модуль-поведение, создадим его. Но сначала нам надо определиться с концепцией. Будем считать, что поведение налагает следующее требование. У модуля должны быть две функции: privet/0 и poka/0. С помощью одной функции модуль будет здороваться, а с помощью другой — прощаться. У нас будет два модуля (два “человека”), один будет разговаривать на английском, а другой на русском.

Вот всё содержание файла poved.erl:

-module(poved).
-export([behaviour_info/1]).

behaviour_info(callbacks) -> [{privet,0}, {poka,0}].

Первая строка, как и у любого другого модуля, обозначает название текущего модуля. Вторая строка экспортирует функцию behaviour_info/1. Потом сама эта функция.

Эта функция очень важна — в ней определяются все те функции (с указанием арности), которые должны быть у обладателей поведения (колбэки). Когда компилятор будет собирать другие модули с этим поведением, он будет вызывать функцию behaviour_info/1, причём именно с аргументом callbacks. Эта функция должна вернуть список кортежей, каждый из которых состоит из двух элементов: атом названия функции и арность.

Вот и всё. Минимальное поведение мы определили. Скомпилируем его сразу:

$ erlc poved.erl

Мы получили файл poved.beam. Дальнейшую разработку мы продолжим в этом же каталоге.

Теперь создадим модуль homo1, “разговаривающий” на английском языке:

-module(homo1). 
-behaviour(poved). 
-export([privet/0,poka/0]).

privet() -> 
    Phrase = "Hello",
    io:format("~ts~n", [Phrase]).

poka() -> 
    Phrase = "Goodbye",
    io:format("~ts~n", [Phrase]).

Выглядит он как обычный модуль, вот только во второй строке мы определили, что у данного модуля должно быть поведение poved). В этом месте компилятор запускает команду poved:behaviour_info(callbacks). Так он узнает, что есть требование: должны быть определены две функции, privet/0 и poka/0.

Если этих функций не окажется в данном модуле, или они не будут экспортированы, или будет другая арность, будет ошибка (а точнее, варнинг). Вот в общем-то и всё. Скомпилируем модуль и запустим его:

$ erlc -pa . homo1.erl
$ erl -noshell -s homo1 privet -s homo1 poka -s init stop
Hello
Goodbye

Всё работает, как и задумано. Теперь создадим второй модуль, который разговаривает уже на русском.

-module(homo2). 
-behaviour(poved). 
-export([privet/0,poka/0]).

privet() -> 
    Phrase = "Здравствуйте",
    io:format("~ts~n", [Phrase]).

poka() -> 
    Phrase = "До свидания",
    io:format("~ts~n", [Phrase]).

Различия, как мы видим, минимальные. Тоже скомпилируем и запустим.

$ erlc -pa . homo2.erl 
$ erl -noshell -s homo2 privet -s homo2 poka -s init stop
Здравствуйте
До свидания

Таким образом, когда создаём однотипные модули, у которых есть одинаковый набор функций, имеет смысл задать общее требование (поведение). Хотя бы потому, что всё меняется, и мы потом можем захотеть всем этим однотипным модулям добавить ещё какую-либо функцию (например, spasibo/0). Изменив поведение, мы усилим требование, и если какому-то модулю забудем добавить эту функцию, компилятор нам напомнит.


© Алексей Карманов, 2024.