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
Hello!
Suppose you are building workflow (order / task / payment) processing system with the following requirements:
Each workflow con...
New
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app?
Looking for hints regarding:
Addi...
New
Kia ora,
We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
Hi all, I wanted to ask how the community is dealing with post-release steps.
Today we have Ecto migrations, which make sure that the db...
New
Hello,
I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
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
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 9 of 9 Posts
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?