Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create and use a custom Erlang behavior?

Tags:

erlang

Trying to define a custom behaviour in Erlang, I cannot figure out a way to apply the callback function inside the behaviour definition module. The compiler claims, the callback function is undefined.

I was expecting a callback function in a behaviour to work just like an abstract method in an OO language where it is possible to use the method without specifying the implementation.

The below example defines a callback function fn. Then, that function is used in add_one. What fn actually does is controlled by the Erlang module implementing this behaviour.

-module( mybeh ).

-callback fn( A::number() ) -> B::number().

-export( [add_one/1] ).

add_one( A ) ->
  1+fn( A ).

But when I try to compile the file mybeh.erl, I get the following error message:

$ erlc mybeh.erl
mybeh.erl:8: function fn/1 undefined

Code examples I found on erlangcentral.org, learnyousomeerlang.com or metajack.im were too simplistic to cover this case. Neither did I have any luck with grepping my way through well known Erlang projects on Github (could have tried harder though).

like image 343
Jörgen Brandt Avatar asked Sep 01 '15 16:09

Jörgen Brandt


1 Answers

You are very close. It is actually easier than what you tried:

-module(my_behavior).

-callback fn(A :: term()) -> B :: term().

The compiler can understand this fully, just as it is.

So easy, it's sort of anticlimactic.

EDIT

"Cool story, how to use?"

Nothing speaks like a working example:

Here we have an abstract service. It is supposed to respond to a very narrow spectrum of messages, and ridicule anything else. Its special element, though, is that it accepts as its startup argument the name of a module which defines some special aspect of its behavior -- and this is the callback module.

-module(my_abstract).
-export([start/1]).

start(CallbackMod)->                
    spawn(fun() -> loop(CallbackMod) end).

loop(CBM) ->
    receive
        {Sender, {do_it, A}} ->
            Sender ! CBM:fn(A),
            loop(CBM);
        stop ->
            io:format("~p (~p): Farewell!~n",
                      [self(), ?MODULE]);
        Message ->
            io:format("~p (~p): Received silliness: ~tp~n",
                      [self(), ?MODULE, Message]),
            loop(CBM)
    end.

So here we define a really simple callback module, in accordance with the behavior defined as 'my_behavior' above:

-module(my_callbacks).
-behavior(my_behavior).
-export([fn/1]).

fn(A) -> A + 1.

Here it is in action!

1> c(my_behavior).
{ok,my_behavior}
2> c(my_abstract).
{ok,my_abstract}
3> c(my_callbacks).
{ok,my_callbacks}
4> Service = my_abstract:start(my_callbacks).
<0.50.0>
5> Service ! {self(), {do_it, 5}}.
{<0.33.0>,{do_it,5}}
6> flush().
Shell got 6
ok
7> Service ! {self(), {do_it, 41}}.
{<0.33.0>,{do_it,41}}
8> flush().                        
Shell got 42
ok
9> Service ! stop.
<0.50.0> (my_abstract): Farewell!
stop

So what good is the behavior definition? It didn't actually do anything! Well, what good are all those Dialyzer type hierarchy declarations? They don't do anything either. But they help you automatically check your work to make sure you won't experience some exciting runtime fail -- but neither Dialyzer nor behavior definitions force you to do anything: they merely warn us of our (likely) impending doom:

-module(my_other_callbacks).
-behavior(my_behavior).
-export([haha_wtf/1]).

haha_wtf(A) -> A - 1.

And when we build this we get:

10> c(my_other_callbacks).
my_other_callbacks.erl:2: Warning: undefined callback function fn/1 (behaviour 'my_behavior')
{ok,my_other_callbacks}

Note, though, that this module was actually compiled and is still independently useable (but not by our abstract service, which is expecting to find fn/1 defined in anything called a my_behavior):

11> my_other_callbacks:haha_wtf(5).
4

Hopefully this little walkthrough sheds some light on the path.

like image 139
zxq9 Avatar answered Oct 17 '22 04:10

zxq9