I was just looking for documentation to build a Behaviour with some defaults.
I stumbled upon this thread:
As it seems, that’s still true in 2021. I did not find any documentation (except for the basics).
So I had a look at gen_server.ex and put a sample together (see below, see the tests for how I understand things). It does what I expect, but I have some questions:
- am I missing some important technique?
- (Solved with module attributes) the way I access the
opts
seems a little odd…!? - (Solved by adding @impl inside using) I get this warning I can’t figure out:
warning: module attribute @impl was not set for function foo/0 callback (specified in MyBehaviour). This either means you forgot to add the “@impl true” annotation before the definition or that you are accidentally overriding this callback
lib/user.ex:2: User (module)
my_behaviour.ex
defmodule MyBehaviour do
@callback foobar(foo_arg :: any()) :: any()
@callback foo() :: any()
@callback bar(bar_arg :: any()) :: any()
@callback baz(baz_arg1 :: any(), baz_arg2 :: any()) :: any()
@optional_callbacks foo: 0,
bar: 1,
baz: 2
defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
@behaviour MyBehaviour
@foo_opt Keyword.get(opts, :foo, :no_foo_opt)
@bar_opt Keyword.get(opts, :bar, :no_bar_opt)
@baz_opt Keyword.get(opts, :baz, :no_baz_opt)
@impl true
def foo() do
{:foo, @foo_opt}
end
@impl true
def bar(bar_arg) when is_atom(bar_arg) do
{:bar, bar_arg, @bar_opt}
end
@impl true
def baz(baz_arg1, baz_arg2) do
{:baz, baz_arg1, baz_arg2, @baz_opt}
end
defoverridable baz: 2, foo: 0
end
end
def behaviour_fun(behaviour_fun_arg) do
{:behaviour_fun, behaviour_fun_arg}
end
end
user.ex
defmodule User do
use MyBehaviour, bar: :opt_for_bar, foo: :opt_for_foo
@impl true
def bar(arg) do
{:bar_implemented, arg}
end
@impl true
def baz(arg1, arg2) do
{:baz_implemented, arg1, arg2}
end
@impl true
def foobar(arg) do
MyBehaviour.behaviour_fun(arg)
end
end
test.exs
defmodule Test do
use ExUnit.Case
test "default impl is called and option used" do
assert {:foo, :opt_for_foo} = User.foo()
end
test "default impl is called when it matches" do
assert {:bar, :some_atom, :opt_for_bar} = User.bar(:some_atom)
end
test "user impl is called when default doesn't match" do
assert {:bar_implemented, 1} = User.bar(1)
end
test "user impl is always called with defoverrideable" do
{:baz_implemented, 1, 2} = User.baz(1, 2)
end
test "can define function in behaviour" do
{:behaviour_fun, 1} = User.foobar(1)
end
end