As I understood from the "Erlang and OTP in action" book, the word behavior refers to:
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).
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.
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.
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.
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With