Fun With Elixir Macros by Daniel Jaouen


Today we’re going to dive into a topic that’s been on my mind lately. And that topic is macro-writing macros!

Let’s take a look at an example:

defmodule Test do
  defmacro test(one, two, three, four) do
    one |> IO.inspect

    quote do
      unquote(two) |> IO.inspect

      defmacro unquote(one)(five, six) do
        unquote(three) |> IO.inspect
        four = unquote(four)
        quote do
          [unquote(four), unquote(five), unquote(six)]

defmodule Test.Test do
  require Test

  Test.test(:one, :two, :three, :four)

In this example, we see that we have a macro-writing macro. The module Test.Test calls the macro Test.test, which takes four arguments. The macro then prints the value of one and then enters a quoted block and prints out the value of unquote(two). The macro then defines another macro, which takes two arguments and whose name is unquote(one). This new macro then prints out the value of unquote(three) and assigns unquote(four) to four. The macro then returns a quoted list containing three elements (four, five, and six, all unquoted).

Let’s load this up in IEx and see what happens.

○ iex -S mix
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Compiling 1 file (.ex)
Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> require Test.Test
iex(2)>, :six)
[:four, :five, :six]

We see immediately that :one and :two are printed out. Then, when we invoke, :six), :three is printed out and the return value is [:four, :five, :six].


What we can take away from this example is that evaluation happens one step at a time. The Elixir runtime evals all code up to the inner-most quote block and interpolates all unquoted variables not included in that block. Then, when we call that macro (and only then), four, five, and six are interpolated. Note that we don’t have access to one, two, or three in the inner-most quoted block and we only have access to four because we unquoted it in the inner-most macro definition.

And that’s it. Feel free to fire up an instance of IEx and try it out for yourself.