skateinmars
Hello, I work with a team of Elixir devs and some time ago we came across a code pattern within the Hex.pm website source that has an elegant solution for configuring which implementation is used within the behaviour: hexpm/lib/hexpm/billing/billing.ex at main · hexpm/hexpm · GitHub
This solution fits the “explicit contracts” philosophy and works great, since you can for instance simply call Hexpm.Billing.get(organization) in a controller and not have to know about using Application.get_env(:hexpm, :billing_impl) everywhere.
As you can see from the code though, you need to write a definition for each callback function, that will simply delegate to impl().
I tried to automate some of that work using metaprogramming, but I hit a roadblock: I have no way of accessing the callback attributes during compilation!
Here is my sample module:
defmodule Foobar do
@moduledoc """
Behaviour for Foobar.
"""
@doc """
Reticulates the spline.
"""
@callback reticulate() :: :ok | {:error, any()}
defp impl(),
do: Application.get_env(:myapp, :foobar_impl, Foobar.InMemory)
# I want this to be autogenerated:
# def reticulate(), do: impl().reticulate()
end
defmodule Foobar.InMemory do
@behaviour Foobar
@impl true
def reticulate do
:ok
end
end
I am able to use the behaviour_info function once the module is compiled:
Foobar.behaviour_info(:callbacks) |> IO.inspect(label: "callbacks list after compilation")
# => callbacks list after compilation: [reticulate: 0]
But this function is not yet accessible from the module while I’m trying to write a macro.
My other solution would have been to access the @callback attributes directly, but it seems that typespecs attributes have a special meaning to the compiler, and I cannot access them:
defmodule Implementor do
defmacro list_callbacks() do
quote do
Module.get_attribute(__MODULE__, :callback)
|> IO.inspect(label: "callback attribute")
Module.get_attribute(__MODULE__, :notcallback)
|> IO.inspect(label: "notcallback attribute")
end
end
end
defmodule Foobar do
require Implementor
@callback reticulate() :: :ok | {:error, any()}
@notcallback "reticulate"
Implementor.list_callbacks()
end
# => callback attribute: nil
# => notcallback attribute: "reticulate"
Is there any solution I may have missed?
As a workaround, I thought about generating a second module (Foobar.Impl) using an after_compile callback, but I found the solution of putting behaviour and implementation in the same module a bit more elegant.
Trending in Questions
Other Trending Topics
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
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security











Showing Posts 1 to 2- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
jola
It’s because you put the two modules in the same file. I split it into two and
skateinmars
Thanks, that did the trick!
Macros are still mostly magic for me but I managed to write a usable module. Here is the end result: