Erlang / OTP поведения для начинающих - PullRequest
30 голосов
/ 26 апреля 2011

Как я понял из книги "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, "\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).

Ответы [ 5 ]

21 голосов
/ 27 апреля 2011

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

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

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

Делегат поведенияобработка сообщений для модулей обратного вызова или поведение реализации , как их вызывают «Erlang and 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, вы найдете цикл намногокак это, но они намного сложнее из-за обработки системных сообщений, тайм-аутов, трассировки, исключений и т. д. Стандартные поведения также запускают свои циклы в отдельном процессе, поэтому они также содержат код для запуска процесса цикла и передачи ему сообщений..

19 голосов
/ 26 апреля 2011

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

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

Позволяет компилятору выдавать сообщения об ошибках, относящиеся к поведению, например: gen_server

Q: Что такое "функция обратного вызова"действительно означает в контексте Elang / OTP?

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

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

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

Да ... возможно ... нет :)

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

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

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

12 голосов
/ 26 апреля 2011

Что должен знать начинающий 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)
  ...
...
3 голосов
/ 26 апреля 2011

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

0 голосов
/ 26 апреля 2011

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

...