🎓 A1Learner
🔍
0 of 46 lessons completed

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?