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)] end end end end end defmodule Test.Test do require Test Test.test(:one, :two, :three, :four) end
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
four. The macro then returns a quoted list containing three elements (
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) :one :two Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> require Test.Test Test.Test iex(2)> Test.Test.one(:five, :six) :three [:four, :five, :six] iex(3)>
We see immediately that
:two are printed out. Then, when we invoke
: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),
six are interpolated. Note that we don’t have access to
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.