Поведение Erlang/OTP для начинающих

Как я понял из книги "Эрланг и ОТП в действии", слово поведение относится к:

  • интерфейс поведения, который представляет собой набор функций;
  • реализация поведения, которая является кодом приложения (модулем обратного вызова);
  • контейнер поведения, который является процессом.

вопрос:

что Новичок 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, "rn$", "", [{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).

5 ответов


Q: что Новичок Erlang/OTP должен знать о поведении? Это возможно описать и понять понятие поведения OTP в в двух словах?

поведение обычно используется в коде, чтобы компилятор мог генерировать более интуитивные сообщения об ошибках в зависимости от его поведения i.e приложение / супервизор / gen_server/gen_event / gen_fsm.

это позволяет компилятору давать сообщения об ошибках, специфичные для поведения для ex: gen_server

Q: что на самом деле означает "функция обратного вызова" в контексте Эланг / ОТП?

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

таким образом, когда, например. экспортируемой функции gen_server вызывается из другого модуля, эта функция может иметь функцию обратного вызова (handle_call/handle_cast), имеющие разные шаблоны.

Q: можем ли мы рассматривать обратные вызовы в реализации поведения как методы переопределен на Java?

Да...может быть...нет:)

Q: в книге говорится, что связанная функция обратного вызова для библиотеки функции gen_server:start_link/4' в следующем коде 'Модуль: init / 1'.

gen_server:start_link вызывает init функция сама по себе, как ответил w55.... (извините, довольно большое имя).


надеюсь, я ответил на все ваши вопросы :)


вместо того, чтобы пытаться решить ваши конкретные вопросы, как это уже сделали другие ответы, я попытаюсь объяснить в простых терминах основы поведения и позволить вам ответить на ваши собственные вопросы, основанные на понимании этих основ.

поведение в основном является фреймворком обработки сообщений, где под "фреймворком" я подразумеваю классическое определение частичного решения проблемы, которое может быть завершено и настроено конечным пользователем. Поведение OTP по существу поставка:

  • цикл обработки сообщений
  • интеграция с базовой поддержкой OTP для обновления кода, трассировки, системных сообщений и т. д.

поведение делегирует обработку сообщений модулям обратного вызова или поведение реализации как их называет "Эрланг и ОТП в действии". По призыву его 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, вы найдете цикл, похожий на этот, но они намного сложнее из-за обработки системных сообщений, таймаутов, трассировки, исключений и т. д. Норматив поведение также запускает свои циклы в отдельном процессе, поэтому они также содержат код для запуска процесса цикла и передачи ему сообщений.


что Новичок 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, будет вызываться функциональный модуль:init/1, где Module-второй параметр, который вы передали функция start_link, с аргументами, которые вы предоставили в качестве четвертого аргумента. Другими словами, это то, что происходит за кулисами start_link/4:

...
start_link(Name, Module, Args, Opts) ->
  ...
  Module:init(Args)
  ...
...

посмотрите исходный код модуля gen_server в каталоге Erlang lib. Это очень хорошо объясняется в исходном коде, комментарии очень сложные.


gen_server:start_link вызывает init.