0

That's my suspicion as this simple code

-module(simple_server).
-export( [sayHello/0] ).

-callback say(Num :: term()) -> term().

sayHello() ->
    io:fwrite( "Hello 1: ~p\n", [ say(1) ]) ,
    io:fwrite( "Hello 2: ~p\n", [ say(2) ]) .

fails to be compiled:

$ erlc simple_server.erl
simple_server.erl:7: function say/1 undefined
simple_server.erl:8: function say/1 undefined

If that is the case, then this is not explicitly commented elsewhere: official docs, "learn erlang", this answer.

cibercitizen1
  • 20,944
  • 16
  • 72
  • 95
  • This question https://stackoverflow.com/questions/32336854/how-to-create-and-use-a-custom-erlang-behavior?noredirect=1&lq=1 exposes the same problem without clearly showing how callbacks are supposed to be called. – cibercitizen1 Nov 14 '18 at 09:58
  • In the accepted answer, the callback function is called as `CBM:fn(A)`. – legoscia Nov 14 '18 at 09:59
  • @legoscia Yes. Then, the name of the module implementing the callback must be supplied to the generic code. – cibercitizen1 Nov 14 '18 at 10:02
  • @legoscia If you are sure about the ways a generic module can call a callback (to be provided in the future) you could write an answer here to be accepted. Thanks in advance. – cibercitizen1 Nov 14 '18 at 10:06

2 Answers2

2

You need to provide the name of the callback module, which can be done through apply and spawn, but you can also make a simple call using a variable as the module name, e.g. CallbackModule:say(1).

So you could do either:

sayHello(CallbackModule) ->
    io:fwrite( "Hello 1: ~p\n", [ CallbackModule:say(1) ]) ,
    io:fwrite( "Hello 2: ~p\n", [ CallbackModule:say(2) ]) .

or

sayHello(CallbackModule) ->
    io:fwrite( "Hello 1: ~p\n", [ apply(CallbackModule, say, [1]) ]) ,
    io:fwrite( "Hello 2: ~p\n", [ apply(CallbackModule, say, [2]) ]) .

Those two versions are equivalent.

Let's create a callback module implementing the simple_server behaviour:

-module(my_callback).
-behaviour(simple_server).
-export([say/1]).

say(N) ->
    {N, is, the, loneliest, number}.

Now we can call simple_server:sayHello with the module name as the argument:

> simple_server:sayHello(my_callback).
Hello 1: {1,is,the,loneliest,number}
Hello 2: {2,is,the,loneliest,number}
legoscia
  • 39,593
  • 22
  • 116
  • 167
  • 1
    Thanks a lot! I've also prepared a simple explanatory example, including compile orders, which are very important (-pa flag). – cibercitizen1 Nov 14 '18 at 11:30
1

HOWTO Erlang Custom Behaviors (template method pattern).

-1. Write a generic module. Here, an algorithm is defined, but some steps (callback functions in Erlang nomenclature) are left for a future specific definition.

%% generic.erl   
-module(generic).
-export( [sayHello/1] ).

-callback say(Num :: term()) -> term(). %% future definition

%% generic algorithm: needs the reference to the provider module                                 
sayHello(ProviderModule) ->
  io:fwrite( "Hello 1: ~p\n", [ ProviderModule:say(1) ]) ,
  io:fwrite( "Hello 2: ~p\n", [ ProviderModule:say(2) ]) .

-2. Compile generic.erl

erlc generic.erl

-(3.) Try to write a provider (callback) module

%% callbacks1.erl (fails to implement say/1 callback)
-module( callbacks1 ).
-behaviour( generic ).

-(4.) Compile callbacks1.erl: use -pa . to say where generic.beam is. (Therefore, the expected warning about say/1 is issued).

erlc -pa . callbacks1.erl
callbacks1.erl:2: Warning: undefined callback function say/1 (behaviour 'generic')

(Note: If -pa is not given, you'll got this: "callbacks1.erl:2: Warning: behaviour generic undefined")

-3. Write a correct provider (callback) module.

%% callbacks2.erl
-module( callbacks2 ).
-behaviour( generic ).

-export( [say/1] ).

say(1) -> "good morning";
say(2) -> "bon jour";
say(_) -> "hi".

-4. Compile it

erlc -pa . callbacks2.erl

(Ok now).

-5. Write a main.erl to gather generic module with callback module.

%% main.erl
-module( main ).
-export( [main/0] ).

main() ->
    %% call the generic algorithm telling it what callback module to use
    generic:sayHello( callbacks2 ) 
    . % ()

-6. Compile and run main

erlc main.erl
erl -noshell -s main main -s init stop

We get:

Hello 1: "good morning"
Hello 2: "bon jour"
cibercitizen1
  • 20,944
  • 16
  • 72
  • 95