简体   繁体   English

初学者的Erlang / OTP行为

[英]Erlang/OTP behaviors for beginner

As I understood from the "Erlang and OTP in action" book, the word behavior refers to: 正如我从“Erlang and OTP in action”一书中所理解的那样,行为一词指的是:

  • the behaviour interface, which is a set of functions; 行为界面,这是一组功能;
  • the behaviour implementation, which is the application-specific code (a callback module); 行为实现,即特定于应用程序的代码(回调模块);
  • the behaviour container, which is a process. 行为容器,这是一个过程。

Question: 题:

What an Erlang/OTP beginner should know about behaviours? Erlang / OTP初学者应该了解哪些行为? Is it possible to describe and understand the notion of OTP behaviour in a nutshell? 是否有可能简单地描述和理解OTP行为的概念?

What 'callback function' does actually mean in the context of Elang/OTP? 什么'回调函数'在Elang / OTP的上下文中实际意味着什么?

Can we consider the callbacks in a behaviour implemenation as methods overriden in Java? 我们可以考虑行为实现中的回调,因为Java中的方法会覆盖吗?

The book says that the associated callback function for the library function 'gen_server:start_link/4' in the following code is 'Module:init/1'. 该书说,以下代码中库函数'gen_server:start_link / 4'的关联回调函数是'Module:init / 1'。

Does that mean that with init/1 we call the gen_server:start_link/4 library function? 这是否意味着使用init / 1我们调用gen_server:start_link / 4库函数? Or does that mean anything else? 或者这意味着什么呢?

-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).

Rather than try to address your specific questions as other answers have already done, I'll try to explain in simple terms the basics behind behaviors, and let you answer your own questions based on understanding those basics. 我将尝试用简单的术语解释行为背后的基础知识,并让您在理解这些基础知识的基础上回答自己的问题,而不是像其他答案那样尝试解决您的具体问题。

A behavior is basically a message handling framework, where by "framework" I mean the classical definition of a partial solution to a problem that can be completed and customized by the end user. 行为基本上是一个消息处理框架,其中“框架”是指可以由最终用户完成和定制的问题的部分解决方案的经典定义。 OTP behaviors essentially supply: OTP行为基本上提供:

  • a message loop 消息循环
  • integration with underlying OTP support for code upgrade, tracing, system messages, etc. 与底层OTP集成,支持代码升级,跟踪,系统消息等。

Behaviors delegate message handling to callback modules, or behavior implementations as "Erlang and OTP In Action" calls them. 行为将消息处理委托给回调模块,或者将行为实现委托为“Erlang和OTP In Action”来调用它们。 Upon the invocation of its init/1 function, the callback module generally creates state for the message loop to keep on its behalf. 在调用其init/1函数时,回调模块通常会为消息循环创建状态以代表它。 The behavior loop then passes this state to each subsequent invocation of a callback module message handling function, and each of these invocations can return a modified state. 然后,行为循环将此状态传递给回调模块消息处理函数的每个后续调用,并且这些调用中的每一个都可以返回修改后的状态。 Callback functions also return instructions telling the behavior message loop what to do next. 回调函数还返回指令,告诉行为消息循环下一步做什么。

Here's a greatly simplified version of the message loop at the heart of a behavior: 以下是行为核心的消息循环的大大简化版本:

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.

This tail-recursive loop has the Callbacks module and the State variable as arguments. 这个尾递归循环将Callbacks模块和State变量作为参数。 Before this loop is first invoked, you've already told the behavior what your callback module is, and then base OTP behavior support code has already called your init/1 callback function to get the initial value of State . 在首次调用此循环之前,您已经告诉行为您的回调模块是什么,然后基本OTP行为支持代码已经调用了您的init/1回调函数来获取State的初始值。

Our example behavior loop receives messages of form M1 , M2 , and any other message, the details of which don't matter here, and for each message, invokes a different callback function in the Callbacks module. 我们的示例行为循环接收表单M1M2和任何其他消息的消息,其详细信息在此处无关紧要,并且对于每个消息,在Callbacks模块中调用不同的回调函数。 In this example, the handle_m1 and handle_m2 callback functions handle messages M1 and M2 respectively, while the callback handle_other handles all other kinds of messages. 在此示例中, handle_m1handle_m2回调函数分别处理消息M1M2 ,而回调handle_other处理所有其他类型的消息。 Note that State is passed to each callback function. 请注意, State被传递给每个回调函数。 Each function is expected to return a tuple with the first element telling the loop what to do next and the second element containing possible new state for the loop — either the same value as State or a new different value — which the loop stores in its variable NState . 每个函数都应该返回一个元组,第一个元素告诉循环下一步该做什么,第二个元素包含循环的可能新状态 - 与State相同的值或新的不同值 - 循环存储在其变量中NState In this example, if Next is the atom stop , the loop stops, but if it's anything else, the loop invokes itself recursively, passing the new state NState to the next iteration. 在此示例中,如果Next是原子stop ,则循环停止,但如果它是其他任何东西,则循环以递归方式调用自身,将新状态NState传递给下一次迭代。 And since it's tail recursive, the loop won't ever blow out the stack. 由于它是尾递归的,所以循环不会吹出堆栈。

If you dig through the sources of standard OTP behaviors such as gen_server and gen_fsm , you'll find a loop much like this, but they're much more complex due to handling system messages, timeouts, tracing, exceptions, etc. Standard behaviors also start their loops in a separate process, so they also contain code for starting the loop process and passing messages to it. 如果你仔细研究标准OTP行为的来源,比如gen_servergen_fsm ,你会发现很像这样的循环,但是由于处理系统消息,超时,跟踪,异常等,它们要复杂得多。标准行为也是如此在一个单独的进程中启动它们的循环,因此它们还包含用于启动循环进程并将消息传递给它的代码。

Q: What an Erlang/OTP beginner should know about behaviours? 问: Erlang / OTP初学者应该了解哪些行为? Is it possible to describe and understand the notion of OTP behaviour in a nutshell? 是否有可能简单地描述和理解OTP行为的概念?

A behaviour is usually used in code so that the compiler can generate more intuitive error messages depending upon its behaviour ie application/supervisor/gen_server/gen_event/gen_fsm. 行为通常在代码中使用,以便编译器可以根据其行为生成更直观的错误消息,即application / supervisor / gen_server / gen_event / gen_fsm。

It enables the compiler give error messages specific to the behaviour for ex: gen_server 它使编译器能够提供特定于ex:gen_server行为的错误消息

Q: What 'callback function' does actually mean in the context of Elang/OTP? 问:在Elang / OTP环境中,“回调函数”实际意味着什么?

Callback function can be said to be taken from GUI programming (at least similar). 可以说回调函数取自GUI编程(至少类似)。 Whenever an event occurs for ex. 每当事件发生在前。 a mouse click there is a separate function that handles mouse click. 鼠标单击有一个单独的功能,可以处理鼠标点击。

Thus whenever for eg. 因此,无论何时例如。 an exported function of a gen_server is called from another module, that function can have a callback function (handle_call/handle_cast) having different patterns. 从另一个模块调用gen_server的导出函数,该函数可以具有具有不同模式的回调函数(handle_call / handle_cast)。

Q: Can we consider the callbacks in a behaviour implementation as methods overridden in Java? 问:我们可以将行为实现中的回调视为在Java中重写的方法吗?

Yeah...maybe...no :) 是啊......也许......不:)

Q: The book says that the associated callback function for the library function 'gen_server:start_link/4' in the following code is 'Module:init/1'. 问:本书说下面代码中库函数'gen_server:start_link / 4'的关联回调函数是'Module:init / 1'。

gen_server:start_link calls init function by itself as answered by w55.... (sorry quite a big name). gen_server:start_link自己调用init函数,由w55回答....(对不起,这是一个很大的名字)。


Hope I have answered all your queries :) 希望我已回答你所有的疑问:)

What an Erlang/OTP beginner should know about behaviours? Erlang / OTP初学者应该了解哪些行为?

Probably what's written here . 可能是这里写的。

Is it possible to describe and understand the notion of OTP behaviour in a nutshell? 是否有可能简单地描述和理解OTP行为的概念?

Reading from the doc: "Behaviours are formalizations of these common patterns. The idea is to divide the code for a process in a generic part (a behaviour module) and a specific part (a callback module)." 从文档中读取:“行为是这些常见模式的形式化。我们的想法是在通用部分(行为模块)和特定部分(回调模块)中划分流程的代码。”

What 'callback function' does actually mean in the context of Elang/OTP? 什么'回调函数'在Elang / OTP的上下文中实际意味着什么?

Look at the link above where examples of callback functions are provided. 请查看上面的链接,其中提供了回调函数的示例。

Can we consider the callbacks in a behaviour implemenation as methods overriden in Java? 我们可以考虑行为实现中的回调,因为Java中的方法会覆盖吗?

In Java terms, a behaviour would probably be a Java Interface, while a callback would be the implementation of one of the methods defined in the interface. 在Java术语中,行为可能是Java接口,而回调则是接口中定义的方法之一的实现。

The book says that the associated callback function for the library function 'gen_server:start_link/4' in the following code is 'Module:init/1'. 该书说,以下代码中库函数'gen_server:start_link / 4'的关联回调函数是'Module:init / 1'。 Does that mean that with init/1 we call the gen_server:start_link/4 library function? 这是否意味着使用init / 1我们调用gen_server:start_link / 4库函数? Or does that mean anything else? 或者这意味着什么呢?

It means that, every time you call the gen_server:start_link/4, the function Module:init/1 will be called, where Module is the second parameter you passed to the start_link function, with the arguments you provided as the forth argument. 这意味着,每次调用gen_server:start_link / 4时,都会调用函数Module:init / 1,其中Module是传递给start_link函数的第二个参数,其中您提供的参数为第四个参数。 In other words, this is what happens behind the scenes of the start_link/4: 换句话说,这是start_link / 4幕后发生的事情:

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

look at the source code of gen_server module in your erlang lib directory. 查看erlang lib目录中gen_server模块的源代码。 It is very well explained in the source code, the comments are very elaborate. 它在源代码中得到了很好的解释,评论非常精细。

gen_server:start_link调用init。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM