Sebb
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
optsseems 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
Trending in Questions
Hey guys,
I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly
Do you guys have any suggestions what is the best prac...
New
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
Hello!
Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app.
I creat...
New
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
So my question is quite simple and i have found no conclusive answer on forum, google or AI.
Should we use :erlang.float for Integer to ...
New
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
Other Trending Topics
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 9- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
eksperimental
For reading the options, put them in an attribute. Btw, José published an answer related to this Unquoute not working for maps even with Macro.escape - #2 by josevalim
eksperimental
the warning regarding the usage of
@implis because you forgot to set it inside__using__.If you do set it in
__using__and do not set it in your implementation, the compiler will not warn, as it realizes the one inside using is generated code by a macro.Sebb
OK, no warning when I add
@implinside__using__How does GenServer get away with not doing this?
opts-problem is also solved.
thank you!
eksperimental
Good question. I will look into it when I have some time to spare
eksperimental
I have looked into it.
Here is a reduced case:
What happens in that you are definining some functions with
__using__but not all of them are overridable. So in the using context, you do not annotate your functions with@implbut in the implementation you do, so you have a function (bar/1 in your original example) that later gets annotated with@implin the implementation. You are mixing clauses from two different contexts and there for the warning.The solution is to add
bar: 1todefoverridableSebb
This means I have to set
defoverridablefor all callbacks. So this is not possible:i.e. a default impl without
defoverridableis called if it matches (herebaronly matches atom-args) and the user-impl is called otherwise.Also GenServer does not set
defoverridablefor all callbacks:eksperimental
init is set to be overridable.
format_status and handle_continue do not have a default implementation.
I think that pattern is good, if you want to raise on a specific pattern or type being passed, but other than that is it too much magic to define some clauses inside
__using__in another module, and maybe another library, and to define other clauses in the implementation.Just to be clear, what I am saying is that you need to set defoverridable for every callback you are defining inside
__using__, not for every callback in the behaviour. Because if you do not do this, and you do not annotate them with@impl, when you do annotate your implementations with@implyou will get a warning for every callback that you defined in__impl__with no@impl, as it happened in your original post.Sebb
OK, thanks all clear now!
I’m marking this as solution for future reference, but all credit to @eksperimental!
Summary of what I learned:
There are 4 types of functions related to Behaviour-modules
1.1
fun_without_default- mandatory callbacks without default implementation, they need a@callback. They have to be implemented in the User-module.1.2
fun_with_default- mandatory callbacks with default implementation inside__using__. They need a@callbackand have to markeddefoverridable. You can’t forbid to override a function, it kinda works with some interesting effects (see above) but will cause a warning. They may be implemented in the User-module, if not, the default is used.1.3
optional_fun- optional callback, they need a@callbackand have to be marked inoptional_callbacks. They may be implemented in the User-module, if not, its an error if they are called.1.4
behaviour_fun- functions implemented in the Behaviour-module but not inside__using__.To access options
2.1 use
quotewithbind_quoted2.2 put the options into a module attribute inside
__using__lastobelus
This example was very helpful.
Would this be the “best” way to be able to have default functions that can be partially overridden?