In some cases you don’t even need @behaviour
.
For example with GenServer
it is enough implement a simple module with the callback handlers that don’t have a default implementation and then simply specify the (callback) module name when using GenServer.start_link/3
(something that only became apparent to me when I read Replacing GenEvent by a Supervisor + GenServer).
Of course that isn’t recommended practice because @behaviour
gives you a direct clue that you are looking at a callback module (with some very specific constraints).
Also keep in mind that behaviours are an Erlang/OTP concept while module attributes and hygienic macros only exist in the Elixir space (Erlang has it’s own style of macros).
So behaviours with a base implementation in Erlang like gen_server don’t rely on Elixir mechanisms (though conceivably an Elixir wrapper could add it’s own extensions).
My mental model for behaviours revolves around Kernel.apply/3
(or perhaps more accurately :erlang.apply/3
) and specifically its argument types:
@spec apply(m, function_name, args) :: any() when m: module(), function_name: atom(), args: [any()]
i.e.
- the behaviour module establishes the convention which
function_name
s will be called with what type of argumentsargs
. - the callback module
m
implements thefunction_name
s which are capable of processing theargs
as supplied by the behaviour module.