Elixir Before-Compile Hooks
A before-compile hook is code that runs just before a module finishes compiling. It can add functions to the module that is being built.
A hook can add a function
The hook adds injected to the caller. The caller keeps its own function too.
defmodule Injector do
defmacro __before_compile__(_env) do
quote do
def injected(), do: "injected"
end
end
end
defmodule Caller do
@before_compile Injector
def own(), do: "own"
end
IO.puts(Caller.own())
IO.puts(Caller.injected())A hook can read gathered values
Two values gather under one attribute. They arrive newest first, so pear comes before apple.
defmodule Collector do
defmacro __before_compile__(env) do
items = Module.get_attribute(env.module, :collected)
quote do
def all(), do: unquote(Macro.escape(items))
end
end
end
defmodule Caller do
@before_compile Collector
Module.register_attribute(__MODULE__, :collected, accumulate: true)
@collected :apple
@collected :pear
end
IO.inspect(Caller.all())
IO.puts(length(Caller.all()))A hook can see the caller module
The hook reads the caller name from its environment. It inserts that name into the new function.
defmodule Namer do
defmacro __before_compile__(env) do
quote do
def owner(), do: unquote(env.module)
end
end
end
defmodule Caller do
@before_compile Namer
def own(), do: "own"
end
IO.puts(Caller.owner())
IO.puts(Caller.own())Example
defmodule Hook do
defmacro __before_compile__(env) do
items = Module.get_attribute(env.module, :items) |> Enum.reverse()
quote do
def all(), do: unquote(Macro.escape(items))
def injected(), do: "injected"
end
end
end
defmodule Caller do
@before_compile Hook
Module.register_attribute(__MODULE__, :items, accumulate: true)
@items :apple
@items :pear
def own(), do: "own"
end
IO.puts(Caller.own())
IO.puts(Caller.injected())
IO.inspect(Caller.all())Key points
- Hook: a hook is code that runs at a fixed point in compilation.
- Before-compile hook: the hook runs after the module body is read and before the module is finished.
- Attribute: the @before_compile attribute names the module that holds the hook.
- Environment: the hook receives the environment of the module being built.
- Module attribute: an accumulated attribute gathers values in the order they are written, newest first.
In this example, the hook reads the gathered values, reverses them into written order, and adds both a list function and an injected function, while the caller keeps its own function.
Note
the hook code itself is a macro, so it builds its functions with quote.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
When does a before-compile hook run?