Is it possible to avoid having to define init and start_link functions? Basically I have a few modules that will all share the same parts, but also have specific bits as well. I was hoping I could do something like this:
defmodule Base do
defmacro __using__(_opts) do
quote do
use GenServer
import Base
@something null
end
end
def start_link() do
hydrate_agg() # callback
# ... stuff that is common
end
def init() do
# ... stuff that is common across everything
end
end
defmodule Alpha do
use Base
@something "that"
def hydrate_agg do
#stuff
end
# ... callbacks
end
defmodule Beta do
use Base
@something "this"
# ... callbacks
end
But I can’t seem to get it to work, it doesn’t seem to pick up the init or start_link functions. Am I barking up the wrong tree for this?
Define the functions ‘inside’ the macro, like:
defmodule Base do
defmacro __using__(_opts) do
quote do
use GenServer
import Base
@something null
def start_link() do
hydrate_agg() # callback
# ... stuff that is common
end
def init() do
# ... stuff that is common across everything
end
end
end
end
That way they will be placed in the using scope. 
2 Likes
Well that makes sense! 
For some reason, I had it in my head that I shouldn’t define functions inside quote do
!
Thanks!
1 Like
indeed long quoted blocks with many functions are not a good thing. But this is not the case. Just a bare GenServer bootstrap.
Be careful anyway when hiding implementation details in a macro. It raises the WTF level for newcomers and external developers. Sometimes explicit is better.
2 Likes
A proper way to do this would be by defining a custom behaviour. For example:
defmodule Base do
@callback hydrate_agg(...) :: ...
@callback handle_call(...)
# some more callbacks
use GenServer
def start_link(mod, something, opts) do
GenServer.start_link(__MODULE__, {mod, something}, opts)
end
def init({mod, something}) do
# common stuff
inner = mod.hydrate_agg(...)
{:ok, %{inner: inner, mod: mod, something: something})
end
def handle_call(msg, from, %{mod: mod, inner: inner} = state) do
case mod.handle_call(msg, from, inner) do
{:reply, reply, inner} -> {:reply, reply, %{state | inner: inner}
# handle other return values
end
end
end
This means that later it can be used as:
defmodule Alpha do
@behaviour Base
def start_link(), do: Base.start_link(__MODULE__, "that", [])
# callbacks
end
defmodule Beta do
@behaviour Base
def start_link(), do: Base.start_link(__MODULE__, "this", [])
# callbacks
end
This might be a bit more verbose, but in the long run it will provide more flexibility and better error messages.
3 Likes