Skip to content

efcasado/behaviours2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

behaviours2

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.

Examples

Simple behaviour
-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.

gen_server (echo server)

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?