Поведение — произвольное требование к текущему модулю, заключающееся в том, что в модуле должны быть определены некоторые функции определённой арности. То же: 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.