Erlang/OTP behaviors for beginner

2019-03-08 18:34发布

问题:

As I understood from the "Erlang and OTP in action" book, the word behavior refers to:

  • 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? Is it possible to describe and understand the notion of OTP behaviour in a nutshell?

What 'callback function' does actually mean in the context of Elang/OTP?

Can we consider the callbacks in a behaviour implemenation as methods overriden in 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'.

Does that mean that with init/1 we call the gen_server:start_link/4 library function? 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).

回答1:

Q: What an Erlang/OTP beginner should know about behaviours? Is it possible to describe and understand the notion of OTP behaviour in a nutshell?

A behaviour is usually used in code so that the compiler can generate more intuitive error messages depending upon its behaviour i.e application/supervisor/gen_server/gen_event/gen_fsm.

It enables the compiler give error messages specific to the behaviour for ex: gen_server

Q: What 'callback function' does actually mean in the context of Elang/OTP?

Callback function can be said to be taken from GUI programming (at least similar). 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.

Q: Can we consider the callbacks in a behaviour implementation as methods overridden in 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 calls init function by itself as answered by w55.... (sorry quite a big name).


Hope I have answered all your queries :)



回答2:

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:

  • a message loop
  • integration with underlying OTP support for code upgrade, tracing, system messages, etc.

Behaviors delegate message handling to callback modules, or behavior implementations as "Erlang and OTP In Action" calls them. Upon the invocation of its init/1 function, the callback module generally creates state for the message loop to keep on its behalf. 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. 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.

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. 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. Note that State is passed to each callback function. 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. 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. 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.



回答3:

What an Erlang/OTP beginner should know about behaviours?

Probably what's written here.

Is it possible to describe and understand the notion of OTP behaviour in a nutshell?

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?

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?

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.

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'. Does that mean that with init/1 we call the gen_server:start_link/4 library function? 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. In other words, this is what happens behind the scenes of the start_link/4:

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


回答4:

look at the source code of gen_server module in your erlang lib directory. It is very well explained in the source code, the comments are very elaborate.



回答5:

gen_server:start_link calls init.