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.