Kernel.SpecialForms.quote
quote
, go back to Kernel.SpecialForms module for more information.
Gets the representation of any expression.
Examples
iex> quote do
...> sum(1, 2, 3)
...> end
{:sum, [], [1, 2, 3]}
Elixir's AST (Abstract Syntax Tree)
Any Elixir code can be represented using Elixir data structures. The building block of Elixir macros is a tuple with three elements, for example:
{:sum, [], [1, 2, 3]}
The tuple above represents a function call to sum
passing 1, 2 and
3 as arguments. The tuple elements are:
The first element of the tuple is always an atom or another tuple in the same representation.
The second element of the tuple represents metadata.
The third element of the tuple are the arguments for the function call. The third argument may be an atom, which is usually a variable (or a local call).
Besides the tuple described above, Elixir has a few literals that are also part of its AST. Those literals return themselves when quoted. They are:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1, 2] #=> Lists
"strings" #=> Strings
{key, value} #=> Tuples with two elements
Any other value, such as a map or a four-element tuple, must be escaped
(Macro.escape/1
) before being introduced into an AST.
Options
:bind_quoted
- passes a binding to the macro. Whenever a binding is given,unquote/1
is automatically disabled.:context
- sets the resolution context.:generated
- marks the given chunk as generated so it does not emit warnings. Currently it only works on special forms (for example, you can annotate acase
but not anif
).:file
- sets the quoted expressions to have the given file.:line
- sets the quoted expressions to have the given line.:location
- when set to:keep
, keeps the current line and file from quote. Read the "Stacktrace information" section below for more information.:unquote
- whenfalse
, disables unquoting. This means anyunquote
call will be kept as is in the AST, instead of replaced by theunquote
arguments. For example:iex> quote do ...> unquote("hello") ...> end "hello" iex> quote unquote: false do ...> unquote("hello") ...> end {:unquote, [], ["hello"]}
Quote and macros
quote/2
is commonly used with macros for code generation. As an exercise,
let's define a macro that multiplies a number by itself (squared). In practice,
there is no reason to define such a macro (and it would actually be
seen as a bad practice), but it is simple enough that it allows us to focus
on the important aspects of quotes and macros:
defmodule Math do
defmacro squared(x) do
quote do
unquote(x) * unquote(x)
end
end
end
We can invoke it as:
import Math
IO.puts("Got #{squared(5)}")
At first, there is nothing in this example that actually reveals it is a
macro. But what is happening is that, at compilation time, squared(5)
becomes 5 * 5
. The argument 5
is duplicated in the produced code, we
can see this behaviour in practice though because our macro actually has
a bug:
import Math
my_number = fn ->
IO.puts("Returning 5")
5
end
IO.puts("Got #{squared(my_number.())}")
The example above will print:
Returning 5
Returning 5
Got 25
Notice how "Returning 5" was printed twice, instead of just once. This is because a macro receives an expression and not a value (which is what we would expect in a regular function). This means that:
squared(my_number.())
Actually expands to:
my_number.() * my_number.()
Which invokes the function twice, explaining why we get the printed value twice! In the majority of the cases, this is actually unexpected behaviour, and that's why one of the first things you need to keep in mind when it comes to macros is to not unquote the same value more than once.
Let's fix our macro:
defmodule Math do
defmacro squared(x) do
quote do
x = unquote(x)
x * x
end
end
end
Now invoking squared(my_number.())
as before will print the value just
once.
In fact, this pattern is so common that most of the times you will want
to use the bind_quoted
option with quote/2
:
defmodule Math do
defmacro squared(x) do
quote bind_quoted: [x: x] do
x * x
end
end
end
:bind_quoted
will translate to the same code as the example above.
:bind_quoted
can be used in many cases and is seen as good practice,
not only because it helps prevent us from running into common mistakes, but also
because it allows us to leverage other tools exposed by macros, such as
unquote fragments discussed in some sections below.
Before we finish this brief introduction, you will notice that, even though
we defined a variable x
inside our quote:
quote do
x = unquote(x)
x * x
end
When we call:
import Math
squared(5)
x
** (CompileError) undefined variable x or undefined function x/0
We can see that x
did not leak to the user context. This happens
because Elixir macros are hygienic, a topic we will discuss at length
in the next sections as well.
Hygiene in variables
Consider the following example:
defmodule Hygiene do
defmacro no_interference do
quote do
a = 1
end
end
end
require Hygiene
a = 10
Hygiene.no_interference()
a
#=> 10
In the example above, a
returns 10 even if the macro
is apparently setting it to 1 because variables defined
in the macro do not affect the context the macro is executed in.
If you want to set or get a variable in the caller's context, you
can do it with the help of the var!
macro:
defmodule NoHygiene do
defmacro interference do
quote do
var!(a) = 1
end
end
end
require NoHygiene
a = 10
NoHygiene.interference()
a
#=> 1
You cannot even access variables defined in the same module unless you explicitly give it a context:
defmodule Hygiene do
defmacro write do
quote do
a = 1
end
end
defmacro read do
quote do
a
end
end
end
Hygiene.write()
Hygiene.read()
** (RuntimeError) undefined variable a or undefined function a/0
For such, you can explicitly pass the current module scope as argument:
defmodule ContextHygiene do
defmacro write do
quote do
var!(a, ContextHygiene) = 1
end
end
defmacro read do
quote do
var!(a, ContextHygiene)
end
end
end
ContextHygiene.write()
ContextHygiene.read()
#=> 1
Hygiene in aliases
Aliases inside quote are hygienic by default. Consider the following example:
defmodule Hygiene do
alias Map, as: M
defmacro no_interference do
quote do
M.new()
end
end
end
require Hygiene
Hygiene.no_interference()
#=> %{}
Note that, even though the alias M
is not available
in the context the macro is expanded, the code above works
because M
still expands to Map
.
Similarly, even if we defined an alias with the same name before invoking a macro, it won't affect the macro's result:
defmodule Hygiene do
alias Map, as: M
defmacro no_interference do
quote do
M.new()
end
end
end
require Hygiene
alias SomethingElse, as: M
Hygiene.no_interference()
#=> %{}
In some cases, you want to access an alias or a module defined
in the caller. For such, you can use the alias!
macro:
defmodule Hygiene do
# This will expand to Elixir.Nested.hello()
defmacro no_interference do
quote do
Nested.hello()
end
end
# This will expand to Nested.hello() for
# whatever is Nested in the caller
defmacro interference do
quote do
alias!(Nested).hello()
end
end
end
defmodule Parent do
defmodule Nested do
def hello, do: "world"
end
require Hygiene
Hygiene.no_interference()
** (UndefinedFunctionError) ...
Hygiene.interference()
#=> "world"
end
Hygiene in imports
Similar to aliases, imports in Elixir are hygienic. Consider the following code:
defmodule Hygiene do
defmacrop get_length do
quote do
length([1, 2, 3])
end
end
def return_length do
import Kernel, except: [length: 1]
get_length
end
end
Hygiene.return_length()
#=> 3
Notice how Hygiene.return_length/0
returns 3
even though the Kernel.length/1
function is not imported. In fact, even if return_length/0
imported a function with the same name and arity from another
module, it wouldn't affect the function result:
def return_length do
import String, only: [length: 1]
get_length
end
Calling this new return_length/0
will still return 3
as result.
Elixir is smart enough to delay the resolution to the latest
possible moment. So, if you call length([1, 2, 3])
inside quote,
but no length/1
function is available, it is then expanded in
the caller:
defmodule Lazy do
defmacrop get_length do
import Kernel, except: [length: 1]
quote do
length("hello")
end
end
def return_length do
import Kernel, except: [length: 1]
import String, only: [length: 1]
get_length
end
end
Lazy.return_length()
#=> 5
Stacktrace information
When defining functions via macros, developers have the option of choosing if runtime errors will be reported from the caller or from inside the quote. Let's see an example:
# adder.ex
defmodule Adder do
@doc "Defines a function that adds two numbers"
defmacro defadd do
quote location: :keep do
def add(a, b), do: a + b
end
end
end
# sample.ex
defmodule Sample do
import Adder
defadd
end
require Sample
Sample.add(:one, :two)
** (ArithmeticError) bad argument in arithmetic expression
adder.ex:5: Sample.add/2
When using location: :keep
and invalid arguments are given to
Sample.add/2
, the stacktrace information will point to the file
and line inside the quote. Without location: :keep
, the error is
reported to where defadd
was invoked. location: :keep
affects
only definitions inside the quote.
Binding and unquote fragments
Elixir quote/unquote mechanisms provide a functionality called unquote fragments. Unquote fragments provide an easy way to generate functions on the fly. Consider this example:
kv = [foo: 1, bar: 2]
Enum.each(kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end)
In the example above, we have generated the functions foo/0
and
bar/0
dynamically. Now, imagine that we want to convert this
functionality into a macro:
defmacro defkv(kv) do
Enum.map(kv, fn {k, v} ->
quote do
def unquote(k)(), do: unquote(v)
end
end)
end
We can invoke this macro as:
defkv [foo: 1, bar: 2]
However, we can't invoke it as follows:
kv = [foo: 1, bar: 2]
defkv kv
This is because the macro is expecting its arguments to be a
keyword list at compilation time. Since in the example above
we are passing the representation of the variable kv
, our
code fails.
This is actually a common pitfall when developing macros. We are assuming a particular shape in the macro. We can work around it by unquoting the variable inside the quoted expression:
defmacro defkv(kv) do
quote do
Enum.each(unquote(kv), fn {k, v} ->
def unquote(k)(), do: unquote(v)
end)
end
end
If you try to run our new macro, you will notice it won't
even compile, complaining that the variables k
and v
do not exist. This is because of the ambiguity: unquote(k)
can either be an unquote fragment, as previously, or a regular
unquote as in unquote(kv)
.
One solution to this problem is to disable unquoting in the
macro, however, doing that would make it impossible to inject the
kv
representation into the tree. That's when the :bind_quoted
option comes to the rescue (again!). By using :bind_quoted
, we
can automatically disable unquoting while still injecting the
desired variables into the tree:
defmacro defkv(kv) do
quote bind_quoted: [kv: kv] do
Enum.each(kv, fn {k, v} ->
def unquote(k)(), do: unquote(v)
end)
end
end
In fact, the :bind_quoted
option is recommended every time
one desires to inject a value into the quote.