Поведение Erlang/OTP для начинающих
Как я понял из книги "Erlang и OTP in action", поведение слова относится к:
- интерфейс поведения, который представляет собой набор функций;
- реализация поведения, которая представляет собой код приложения (модуль обратного вызова);
- контейнер поведения, который является процессом.
Вопрос:
Что начинающий Erlang/OTP должен знать о поведении? Возможно ли описать и понять понятие поведения OTP в двух словах?
Какая функция обратного вызова действительно означает в контексте Elang/OTP?
Можно ли рассматривать обратные вызовы в реализации поведения, поскольку методы переопределяются в Java?
В книге говорится, что связанная функция обратного вызова для функции библиотеки gen_server: start_link/4 в следующем коде: "Module: init/1".
Означает ли это, что с init/1 мы вызываем библиотечную функцию gen_server: start_link/4? Или это означает что-нибудь еще?
-module(tr_server).
-behaviour(gen_server).
-include_lib("eunit/include/eunit.hrl").
%% API
-export([
start_link/1,
start_link/0,
get_count/0,
stop/0
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(DEFAULT_PORT, 1055).
-record(state, {port, lsock, request_count = 0}).
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
%% @doc Starts the server.
%%
%% @spec start_link(Port::integer()) -> {ok, Pid}
%% where
%% Pid = pid()
%% @end
%%--------------------------------------------------------------------
start_link(Port) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).
%% @spec start_link() -> {ok, Pid}
%% @doc Calls `start_link(Port)' using the default port.
s tart_link() ->
start_link(?DEFAULT_PORT).
%%--------------------------------------------------------------------
%% @doc Fetches the number of requests made to this server.
%% @spec get_count() -> {ok, Count}
%% where
%% Count = integer()
%% @end
%%--------------------------------------------------------------------
get_count() ->
gen_server:call(?SERVER, get_count).
%%--------------------------------------------------------------------
%% @doc Stops the server.
%% @spec stop() -> ok
%% @end
%%--------------------------------------------------------------------
stop() ->
gen_server:cast(?SERVER, stop).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([Port]) ->
{ok, LSock} = gen_tcp:listen(Port, [{active, true}]),
{ok, #state{port = Port, lsock = LSock}, 0}.
handle_call(get_count, _From, State) ->
{reply, {ok, State#state.request_count}, State}.
handle_cast(stop, State) ->
{stop, normal, State}.
handle_info({tcp, Socket, RawData}, State) ->
do_rpc(Socket, RawData),
RequestCount = State#state.request_count,
{noreply, State#state{request_count = RequestCount + 1}};
handle_info(timeout, #state{lsock = LSock} = State) ->
{ok, _Sock} = gen_tcp:accept(LSock),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
do_rpc(Socket, RawData) ->
try
{M, F, A} = split_out_mfa(RawData),
Result = apply(M, F, A),
gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Result]))
catch
_Class:Err ->
gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Err]))
end.
split_out_mfa(RawData) ->
MFA = re:replace(RawData, "\r\n$", "", [{return, list}]),
{match, [M, F, A]} =
re:run(MFA,
"(.*):(.*)\s*\\((.*)\s*\\)\s*.\s*$",
[{capture, [1,2,3], list}, ungreedy]),
{list_to_atom(M), list_to_atom(F), args_to_terms(A)}.
args_to_terms(RawArgs) ->
{ok, Toks, _Line} = erl_scan:string("[" ++ RawArgs ++ "]. ", 1),
{ok, Args} = erl_parse:parse_term(Toks),
Args.
%% test
start_test() ->
{ok, _} = tr_server:start_link(1055).
Ответы
Ответ 1
Q: Что должен знать начинающий Erlang/OTP о поведении? Это возможно описать и понять понятие поведения ОТП в скорлупа?
Поведение обычно используется в коде, так что компилятор может генерировать более интуитивные сообщения об ошибках в зависимости от его поведения. я application/supervisor/gen_server/gen_event/gen_fsm.
Это позволяет компилятору давать сообщения об ошибках, специфичные для поведения ex: gen_server
Q: Что такое функция обратного вызова, фактически означает в контексте Elang/OTP?
Можно сказать, что функция обратного вызова берется из программирования GUI (по крайней мере, аналогично). Всякий раз, когда происходит событие ex. щелчок мыши - это отдельная функция, которая обрабатывает щелчок мышью.
Таким образом, всякий раз, когда напр. экспортированная функция gen_server вызывается из другого модуля, эта функция может иметь функцию обратного вызова (handle_call/handle_cast), имеющую разные шаблоны.
Q: Можно ли рассматривать обратные вызовы в реализации поведения как методы переопределено в Java?
Да... может быть... нет:)
Q: В книге говорится, что связанная функция обратного вызова для библиотеки function 'gen_server: start_link/4' в следующем коде 'Модуль: инициализации /1'.
gen_server: start_link вызывает функцию init самостоятельно, как ответил w55.... (извините довольно большое имя).
Надеюсь, что я ответил на все ваши запросы:)
Ответ 2
Вместо того, чтобы пытаться решить ваши конкретные вопросы по мере того, как уже были сделаны другие ответы, я попытаюсь объяснить в простых выражениях основы поведения, и позвольте вам ответить на ваши собственные вопросы, основываясь на понимании этих основ.
Поведение в основном представляет собой платформу обработки сообщений, где под "каркасом" я подразумеваю классическое определение частичного решения проблемы, которое может быть выполнено и настроено конечным пользователем. Поведение OTP по существу обеспечивает:
- цикл сообщений
- интеграция с базовой поддержкой OTP для обновления кода, отслеживания, системных сообщений и т.д.
Обработка сообщений делегаций делегатов для модулей обратного вызова или реализации поведения, так как их называют "Erlang и OTP In Action". При вызове его функции init/1
модуль обратного вызова обычно создает состояние для цикла сообщений, которое должно оставаться от его имени. Затем цикл поведения передает это состояние каждому последующему вызову функции обработки сообщения модуля обратного вызова, и каждый из этих вызовов может возвращать измененное состояние. Функции обратного вызова также возвращают инструкции, описывающие цикл сообщений о действиях, что делать дальше.
Здесь очень упрощенная версия цикла сообщений в основе поведения:
loop(Callbacks, State) ->
{Next, NState} =
receive
M1 ->
Callbacks:handle_m1(M1,State);
M2 ->
Callbacks:handle_m2(M2,State);
Other ->
Callbacks:handle_other(Other,State)
end,
case Next of
stop -> ok;
_ -> loop(Callbacks, NState)
end.
Этот хвосто-рекурсивный цикл имеет в качестве аргументов модуль Callbacks
и переменную State
. Прежде чем этот цикл будет вызываться первым, вы уже рассказали о поведении вашего модуля обратного вызова, а затем базовый код поддержки поведения OTP уже вызвал вашу функцию обратного вызова init/1
, чтобы получить начальное значение State
.
Наш примерный цикл поведения принимает сообщения формы M1
, M2
и любое другое сообщение, подробности которого здесь не имеют значения, и для каждого сообщения вызывает другую функцию обратного вызова в модуле Callbacks
, В этом примере функции обратного вызова handle_m1
и handle_m2
обрабатывают сообщения M1
и M2
соответственно, а обратный вызов handle_other
обрабатывает все другие типы сообщений. Обратите внимание, что State
передается каждой функции обратного вызова. Ожидается, что каждая функция вернет кортеж с первым элементом, сообщающим циклу, что делать дальше, и вторым элементом, содержащим возможное новое состояние для цикла — либо то же значение, что и State
, либо новое другое значение; который цикл хранит в своей переменной NState
. В этом примере, если Next
является атомом stop
, цикл останавливается, но если он что-то еще, цикл вызывает себя рекурсивно, передавая новое состояние NState
на следующую итерацию. И так как он хвост рекурсивный, цикл никогда не ударит стек.
Если вы выкапываете источники стандартного поведения OTP, такие как gen_server
и gen_fsm
, вы найдете такой цикл, похожий на этот, но они намного сложнее из-за обработки системных сообщений, тайм-аутов, трассировки, исключения и т.д. Стандартное поведение также запускает их циклы в отдельном процессе, поэтому они также содержат код для запуска цикла и передачи ему сообщений.
Ответ 3
Что начинающий Erlang/OTP должен знать о поведении?
Возможно, что написано здесь.
Можно ли описать и понять понятие поведения OTP в двух словах?
Чтение из документа: "Поведение - это формализация этих общих шаблонов. Идея состоит в том, чтобы разделить код для процесса в общей части (модуле поведения) и определенной части (модуль обратного вызова)".
Какая функция обратного вызова действительно означает в контексте Elang/OTP?
Посмотрите на ссылку выше, где приведены примеры функций обратного вызова.
Можно ли рассматривать обратные вызовы в реализации поведения, поскольку методы переопределяются в Java?
В терминах Java поведение, вероятно, будет интерфейсом Java, а обратным вызовом будет реализация одного из методов, определенных в интерфейсе.
В книге говорится, что связанная функция обратного вызова для библиотечной функции gen_server: start_link/4 в следующем коде: "Module: init/1". Означает ли это, что с init/1 мы вызываем библиотечную функцию gen_server: start_link/4? Или это означает что-нибудь еще?
Это означает, что каждый раз, когда вы вызываете gen_server: start_link/4, вызывается функция Module: init/1, где Module является вторым параметром, который вы передали функции start_link, с аргументами, которые вы предоставили в качестве четвертого аргумент. Другими словами, это то, что происходит за кулисами start_link/4:
...
start_link(Name, Module, Args, Opts) ->
...
Module:init(Args)
...
...
Ответ 4
посмотрите исходный код модуля gen_server в каталоге erlang lib. Это очень хорошо объяснено в исходном коде, комментарии очень сложны.
Ответ 5
gen_server: start_link вызывает init.