Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Erlang/OTP behaviors for beginner

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). 
like image 689
Kirill Avatar asked Apr 26 '11 13:04

Kirill


People also ask

What is behavior in Erlang?

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). The behaviour module is part of Erlang/OTP.

What is callback function in Erlang?

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.

What is Erlang OTP 23?

Erlang/OTP 23.3 is the third and final maintenance patch package for OTP 23, with mostly bug fixes as well as a few improvements. A full list of bug fixes and improvements in the readme.

What is Erlang used for?

Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging.


1 Answers

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.

like image 185
Steve Vinoski Avatar answered Sep 28 '22 20:09

Steve Vinoski