How to create and use a custom Erlang behavior?

2019-01-23 19:53发布

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).

标签: erlang
1条回答
劳资没心,怎么记你
2楼-- · 2019-01-23 20:30

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, its 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.

查看更多
登录 后发表回答