Gets the representation of any expression.
Examples
quote do: sum(1, 2, 3)
#=> {:sum, [], [1, 2, 3]}
Explanation
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).
Options
:unquote
- when false, disables unquoting. Useful when you have a quote
inside another quote and want to control what quote is able to unquote.
:location
- when set to :keep
, keeps the current line and file from
quote. Read the Stacktrace information section below for more
information.
:context
- sets the resolution context.
:bind_quoted
- passes a binding to the macro. Whenever a binding is
given, unquote
is automatically disabled.
Quote literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1, 2] #=> Lists
"strings" #=> Strings
{key, value} #=> Tuples with two elements
Quote and macros
quote
is commonly used with macros for code generation. As an exercise,
let's define a macro that multiplies a number by itself (squared). Note
there is no reason to define such as 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
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 square(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
:
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 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 #=> ** (RuntimeError) undefined function or variable: x
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
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 does 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
require NoHygiene
a = 10
NoHygiene.interference
a #=> 1
Note that 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 function or variable: a
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 HashDict, as: D
defmacro no_interference do
quote do: D.new
end
end
require Hygiene
Hygiene.no_interference #=> #HashDict<[]>
Notice that, even though the alias D
is not available
in the context the macro is expanded, the code above works
because D
still expands to HashDict
.
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 HashDict, as: D
defmacro no_interference do
quote do: D.new
end
end
require Hygiene
alias SomethingElse, as: D
Hygiene.no_interference #=> #HashDict<[]>
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
# This will expand to Nested.hello for
# whatever is Nested in the caller
defmacro interference do
quote do: alias!(Nested).hello
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_size do
quote do
size("hello")
end
end
def return_size do
import Kernel, except: [size: 1]
get_size
end
end
Hygiene.return_size #=> 5
Notice how return_size
returns 5 even though the size/1
function is not imported. In fact, even if return_size
imported
a function from another module, it wouldn't affect the function
result:
def return_size do
import Dict, only: [size: 1]
get_size
end
Calling this new return_size
will still return 5 as result.
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call size("hello")
inside quote,
but no size/1
function is available, it is then expanded in
the caller:
defmodule Lazy do
defmacrop get_size do
import Kernel, except: [size: 1]
quote do
size([a: 1, b: 2])
end
end
def return_size do
import Kernel, except: [size: 1]
import Dict, only: [size: 1]
get_size
end
end
Lazy.return_size #=> 2
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
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. Note location: :keep
affects
only definitions inside the quote.
Binding and unquote fragments
Elixir quote/unquote mechanisms provides 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
does 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.