5 Macros

An Elixir program can be represented by its own data structures. This chapter will describe what those structures look like and how to manipulate them to create your own macros.

5.1 Building blocks of an Elixir program

The building block of Elixir is a tuple with three elements. The function call sum(1,2,3) is represented in Elixir as:

{ :sum, [], [1, 2, 3] }

You can get the representation of any expression by using the quote macro:

iex> quote do: sum(1, 2, 3)
{ :sum, [], [1, 2, 3] }

Operators are also represented as such tuples:

iex> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}

Even a tuple is represented as a call to {}:

iex> quote do: { 1, 2, 3 }
{ :{}, [], [1, 2, 3] }

Variables are also represented using tuples, except the last element is an atom, instead of a list:

iex> quote do: x
{ :x, [], Elixir }

When quoting more complex expressions, we can see the representation is composed of such tuples, which are nested on each other resembling a tree where each tuple is a node:

iex> quote do: sum(1, 2 + 3, 4)
{:sum, [], [1, {:+, [context: Elixir, import: Kernel], [2, 3]}, 4]}

In general, each node (tuple) above follows the following format:

{ tuple | atom, list, list | atom }
  • The first element of the tuple is an atom or another tuple in the same representation;
  • The second element of the tuple is an list of metadata, it may hold information like the node line number;
  • The third element of the tuple is either a list of arguments for the function call or an atom. When an atom, it means the tuple represents a variable.

Besides the node defined above, there are also five Elixir literals that when quoted return themselves (and not a tuple). They are:

:sum         #=> Atoms
1.0          #=> Numbers
[1,2]        #=> Lists
"binaries"   #=> Strings
{key, value} #=> Tuples with two elements

With those basic structures in mind, we are ready to define our own macro.

5.2 Defining our own macro

A macro can be defined using defmacro. For instance, in just a few lines of code we can define a macro called unless which does the opposite of if:

defmodule MyMacro do
  defmacro unless(clause, options) do
    quote do: if(!unquote(clause), unquote(options))
  end
end

Similarly to if, unless expects two arguments: a clause and options:

require MyMacro
MyMacro.unless var, do: IO.puts "false"

However, since unless is a macro, its arguments are not evaluated when it's invoked but are instead passed literally. For example, if one calls:

unless 2 + 2 == 5, do: call_function()

Our unless macro will receive the following:

unless({:==, [], [{:+, [], [2, 2]}, 5]}, { :call_function, [], [] })

Then our unless macro will call quote to return a tree representation of the if clause. This means we are transforming our unless into an if!

There is a common mistake when quoting expressions which is that developers usually forget to unquote the proper expression. In order to understand what unquote does, let's simply remove it:

defmacro unless(clause, options) do
  quote do: if(!clause, options)
end

When called as unless 2 + 2 == 5, do: call_function(), our unless would then literally return:

if(!clause, options)

Which would fail because the clause and options variables are not defined in the current scope. If we add unquote back:

defmacro unless(clause, options) do
  quote do: if(!unquote(clause), unquote(options))
end

unless will then return:

if(!(2 + 2 == 5), do: call_function())

In other words, unquote is a mechanism to inject expressions into the tree being quoted and it is an essential tool for meta-programming. Elixir also provides unquote_splicing allowing us to inject many expressions at once.

We can define any macro we want, including ones that override the built-in macros provided by Elixir. For instance, you can redefine case, receive, +, etc. The only exceptions are Elixir special forms that cannot be overridden, the full list of special forms is available in Kernel.SpecialForms.

5.3 Macros hygiene

Elixir macros have late resolution. This guarantees that a variable defined inside a quote won't conflict with a variable defined in the context where that macro is expanded. For example:

defmodule Hygiene do
  defmacro no_interference do
    quote do: a = 1
  end
end

defmodule HygieneTest do
  def go do
    require Hygiene
    a = 13
    Hygiene.no_interference
    a
  end
end

HygieneTest.go
# => 13

In the example above, even if the macro injects a = 1, it does not affect the variable a defined by the go function. In case the macro wants to explicitly affect the context, it can use var!:

defmodule Hygiene do
  defmacro interference do
    quote do: var!(a) = 1
  end
end

defmodule HygieneTest do
  def go do
    require Hygiene
    a = 13
    Hygiene.interference
    a
  end
end

HygieneTest.go
# => 1

Variables hygiene only works because Elixir annotates variables with their context. For example, a variable x defined at the line 3 of a module, would be represented as:

{ :x, [line: 3], nil }

However, a quoted variable is represented as:

defmodule Sample do
  def quoted do
    quote do: x
  end
end

Sample.quoted #=> { :x, [line: 3], Sample }

Notice that the third element in the quoted variable is the atom Sample, instead of nil, which marks the variable as coming from the Sample module. Therefore, Elixir considers those two variables come from different contexts and handle them accordingly.

Elixir provides similar mechanisms for imports and aliases too. This guarantees macros will behave as specified by its source module rather than conflicting with the target module.

5.4 Private macros

Elixir also supports private macros via defmacrop. As private functions, these macros are only available inside the module that defines them, and only at compilation time. A common use case for private macros is to define guards that are frequently used in the same module:

defmodule MyMacros do
  defmacrop is_even?(x) do
    quote do
      rem(unquote(x), 2) == 0
    end
  end

  def add_even(a, b) when is_even?(a) and is_even?(b) do
    a + b
  end
end

It is important that the macro is defined before its usage. Failing to define a macro before its invocation will raise an error at runtime, since the macro won't be expanded and will be translated to a function call:

defmodule MyMacros do
  def four, do: two + two
  defmacrop two, do: 2
end

MyMacros.four #=> ** (UndefinedFunctionError) undefined function: two/0

5.5 Code execution

To finish our discussion about macros, we are going to briefly discuss how code execution works in Elixir. Code execution in Elixir is done in two steps:

1) All the macros in the code are expanded recursively;

2) The expanded code is compiled to Erlang bytecode and executed

This behavior is important to understand because it affects how we think about our code structure. Consider the following code:

defmodule Sample do
  case System.get_env("FULL") do
    "true" ->
      def full?(), do: true
    _ ->
      def full?(), do: false
  end
end

The code above will define a function full? which will return true or false depending on the value of the environment variable FULL at compilation time. In order to execute this code, Elixir will first expand all macros. Considering that defmodule and def are macros, the code will expand to something like:

:elixir_module.store Sample, fn ->
  case System.get_env("FULL") do
    "true" ->
      :elixir_def.store(Foo, :def, :full?, [], true)
    _ ->
      :elixir_def.store(Foo, :def, :full?, [], false)
end

This code will then be executed, define a module Foo and store the appropriate function based on the value of the environment variable FULL. We achieve this by using the modules :elixir_module and :elixir_def, which are Elixir internal modules written in Erlang.

There are two lessons to take away from this example:

1) a macro is always expanded, regardless if it is inside a case branch that won't actually match when executed;

2) we cannot invoke a function or macro just after it is defined in a module. For example, consider:

defmodule Sample do
  def full?, do: true
  IO.puts full?
end

The example above will fail because it translates to:

:elixir_module.store Sample, fn ->
  :elixir_def.store(Foo, :def, :full?, [], true)
  IO.puts full?
end

At the moment the module is being defined, there isn't yet a function named full? defined in the module, so IO.puts full? will cause the compilation to fail.

5.6 Don't write macros

Although macros are a powerful construct, the first rule of the macro club is don't write macros. Macros are harder to write than ordinary Elixir functions, and it's considered to be bad style to use them when they're not necessary. Elixir already provides elegant mechanisms to write your every day code and macros should be saved as last resort.

With those lessons, we finish our introduction to macros. Next, let's move to the next chapter which will discuss several topics such as documentation, partial application and others.