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).
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.
gen_server:start_link calls init.
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:
This tail-recursive loop has the
Callbacks
module and theState
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 yourinit/1
callback function to get the initial value ofState
.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 theCallbacks
module. In this example, thehandle_m1
andhandle_m2
callback functions handle messagesM1
andM2
respectively, while the callbackhandle_other
handles all other kinds of messages. Note thatState
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 asState
or a new different value — which the loop stores in its variableNState
. In this example, ifNext
is the atomstop
, the loop stops, but if it's anything else, the loop invokes itself recursively, passing the new stateNState
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
andgen_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.Probably what's written here.
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)."
Look at the link above where examples of callback functions are provided.
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.
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:
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
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.
Yeah...maybe...no :)
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 :)