Erlang behaviours on steroids.
behaviours2
allows developers to provide sound defaults for a behaviour's
callbacks. behaviours2
's parse transform will automatically inject a
callback's default implementation unless the user overwrites it by providing
a custom implementation.
-module(my_awesome_behaviour).
%% To avoid problems when using `warnings_as_errors`
-export([export_all]).
-type t1() :: any().
-type t2() :: any().
-callback f1() -> t1().
-callback f2() -> t2().
-callback f3() -> t2().
f1() ->
'default_f1'.
f2() ->
'default_f2'.
f3() ->
f2().
-module(my_awesome_module).
-compile({parse_transform, bhvs2_pt}).
-behaviour(my_awesome_behaviour).
f2() ->
'custom_f2'.
my_awesome_module:f1().
% => default_f1
my_awesome_module:f2().
% => custom_f2
my_awesome_module:f3().
% => custom_f2.
Note that f1
and f3
were automatically injected into my_awesome_module
. Note as well that no
explicit exports for the injected callback functions nor the provided one were required.
The code snippet below illustrates how much effort it would take to write an echo server.
-module(echo_server).
-compile({parse_transform, bhv2_pt}).
-behaviour(gen_server).
-export([handle_call/3]).
handle_call(Msg, From, State) ->
Reply = Msg,
{reply, Msg, State}.
Below is its plain Erlang/OTP counterpart.
-module(echo_server).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-record(state, {}).
-type state() :: #state{}.
-spec init(Args :: []) ->
{ok, state()} |
{ok, state(), timeout()} |
ignore |
{stop, Reason :: term()}.
init([]) ->
{ok, #state{}}.
-spec handle_call(Request :: term(),
From :: {pid(), Tag :: term()},
State :: state()) ->
{reply, Reply :: term(), state()} |
{reply, Reply :: term(), state(), timeout()} |
{noreply, state()} |
{noreply, state(), timeout()} |
{stop, Reason :: term(), Reply :: term(), state()} |
{stop, Reason :: term(), state()}.
handle_call(Msg, _From, State) ->
Reply = Msg,
{reply, Reply, State}.
-spec handle_cast(Msg :: term(),
State :: state()) ->
{noreply, state()} |
{noreply, state(), timeout()} |
{stop, Reason :: term(), state()}.
handle_cast(Msg, State) ->
{noreply, State}.
-spec handle_info(Info :: term(),
State :: state()) ->
{noreply, state()} |
{noreply, state(), timeout()} |
{stop, Reason :: term(), state()}.
handle_info(Info, State) ->
{noreply, State}.
-spec terminate(Reason :: term(),
State :: state()) -> any().
terminate(_Reason, _State) ->
ok.
-spec code_change(OldVsn :: term() | {down, Vsn :: term()},
State :: state(),
Extra :: term()) ->
{ok, NewState :: state()}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Quite a significant difference. Don't you think?