You are very close. Actually it is easier than you tried:
-module(my_behavior). -callback fn(A :: term()) -> B :: term().
The compiler can fully understand this as it is.
So easy, it's kind of anti-climatic.
EDIT
"Cool story, how to use?"
Nothing says as a working example:
Here we have an abstract service. It should respond to a very narrow range of messages and make fun of anything else. Its special element, however, is that it takes as a start argument the name of the module that defines some particular aspect of its behavior - and it is a 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 according to 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's good about defining behavior? It really didn't do anything! What good is these Dialyzer type hierarchy declarations? They donโt do anything either. But they help you automatically check your work to make sure that you donโt encounter some exciting error at runtime - but neither Dialyzer, nor the definition of behavior forces you to do anything: they just warn us about our (probable) impending doom:
-module(my_other_callbacks). -behavior(my_behavior). -export([haha_wtf/1]). haha_wtf(A) -> A - 1.
And when we build it, 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}
Please note, however, that this module was actually compiled and can still be used independently (but not by our abstract service, which expects to find fn/1 defined in everything called my_behavior ):
11> my_other_callbacks:haha_wtf(5). 4
Hopefully this little walkthrough sheds light on the track.