There are three ways to quote something using a Julia function:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
What does "quoting" mean, and what is it good for? Quoting allows us to protect expressions from being interpreted as special forms by Julia. A common use case is when we generate expressions that should contain things that evaluate to symbols. (For example, this macro needs to return a expression that evaluates to a symbol.) It doesn't work simply to return the symbol:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
What's going on here? @mysym
expands to :x
, which as an expression becomes interpreted as the variable x
. But nothing has been assigned to x
yet, so we get an x not defined
error.
To get around this, we must quote the result of our macro:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Here, we have used the Meta.quot
function to turn our symbol into a quoted symbol, which is the result we want.
What is the difference between Meta.quot
and QuoteNode
, and which should I use? In almost all cases, the difference does not really matter. It is perhaps a little safer sometimes to use QuoteNode
instead of Meta.quot
. Exploring the difference is informative into how Julia expressions and macros work, however.
Meta.quot
and QuoteNode
, explainedHere's a rule of thumb:
Meta.quot
;QuoteNode
.In short, the difference is that Meta.quot
allows interpolation within the quoted thing, while QuoteNode
protects its argument from any interpolation. To understand interpolation, it is important to mention the $
expression. There is a kind of expression in Julia called a $
expression. These expressions allow for escaping. For instance, consider the following expression:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
When evaluated, this expression will evaluate 1
and assign it to x
, then construct an expression of the form _ + _
where the _
will be replaced by the value of x
. Thus, the result of this should be the expression 1 + 1
(which is not yet evaluated, and so distinct from the value 2
). Indeed, this is the case:
julia> eval(ex)
:(1 + 1)
Let's say now that we're writing a macro to build these kinds of expressions. Our macro will take an argument, which will replace the 1
in the ex
above. This argument can be any expression, of course. Here is something that is not quite what we want:
julia> macro makeex(arg)
quote
:( x = $(esc($arg)); :($x + $x) )
end
end
@makeex (macro with 1 method)
julia> @makeex 1
quote
x = $(Expr(:escape, 1))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> @makeex 1 + 1
quote
x = $(Expr(:escape, 2))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
The second case is incorrect, because we ought to keep 1 + 1
unevaluated. We fix that by quoting the argument with Meta.quot
:
julia> macro makeex2(arg)
quote
:( x = $$(Meta.quot(arg)); :($x + $x) )
end
end
@makeex2 (macro with 1 method)
julia> @makeex2 1 + 1
quote
x = 1 + 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Macro hygiene does not apply to the contents of a quote, so escaping is not necessary in this case (and in fact not legal) in this case.
As mentioned earlier, Meta.quot
allows interpolation. So let's try that out:
julia> @makeex2 1 + $(sin(1))
quote
x = 1 + 0.8414709848078965
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> let q = 0.5
@makeex2 1 + $q
end
quote
x = 1 + 0.5
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
From the first example, we see that interpolation allows us to inline the sin(1)
, instead of having the expression be a literal sin(1)
. The second example shows that this interpolation is done in the macro invocation scope, not the macro's own scope. That's because our macro hasn't actually evaluated any code; all it's doing is generating code. The evaluation of the code (which makes its way into the expression) is done when the expression the macro generates is actually run.
What if we had used QuoteNode
instead? As you may guess, since QuoteNode
prevents interpolation from happening at all, this means it won't work.
julia> macro makeex3(arg)
quote
:( x = $$(QuoteNode(arg)); :($x + $x) )
end
end
@makeex3 (macro with 1 method)
julia> @makeex3 1 + $(sin(1))
quote
x = 1 + $(Expr(:$, :(sin(1))))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> let q = 0.5
@makeex3 1 + $q
end
quote
x = 1 + $(Expr(:$, :q))
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> eval(@makeex3 $(sin(1)))
ERROR: unsupported or misplaced expression $
in eval(::Module, ::Any) at ./boot.jl:234
in eval(::Any) at ./boot.jl:233
In this example, we might agree that Meta.quot
gives greater flexibility, as it allows interpolation. So why might we ever consider using QuoteNode
? In some cases, we may not actually desire interpolation, and actually want the literal $
expression. When would that be desirable? Let's consider a generalization of @makeex
where we can pass additional arguments determining what comes to the left and right of the +
sign:
julia> macro makeex4(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$$(Meta.quot(left)) + $$$(Meta.quot(right)))
end
end
end
@makeex4 (macro with 1 method)
julia> @makeex4 x=1 x x
quote # REPL[110], line 4:
x = 1 # REPL[110], line 5:
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
julia> eval(ans)
:(1 + 1)
A limitation of our implementation of @makeex4
is that we can't use expressions as either the left and right sides of the expression directly, because they get interpolated. In other words, the expressions may get evaluated for interpolation, but we might want them preserved. (Since there are many levels of quoting and evaluation here, let us clarify: our macro generates code that constructs an expression that when evaluated produces another expression. Phew!)
julia> @makeex4 x=1 x/2 x
quote # REPL[110], line 4:
x = 1 # REPL[110], line 5:
$(Expr(:quote, :($(Expr(:$, :(x / 2))) + $(Expr(:$, :x)))))
end
julia> eval(ans)
:(0.5 + 1)
We ought to allow the user to specify when interpolation is to happen, and when it shouldn't. Theoretically, that's an easy fix: we can just remove one of the $
signs in our application, and let the user contribute their own. What this means is that we interpolate a quoted version of the expression entered by the user (which we've already quoted and interpolated once). This leads to the following code, which can be a little confusing at first, due to the multiple nested levels of quoting and unquoting. Try to read and understand what each escape is for.
julia> macro makeex5(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$(Meta.quot($(Meta.quot(left)))) + $$(Meta.quot($(Meta.quot(right)))))
end
end
end
@makeex5 (macro with 1 method)
julia> @makeex5 x=1 1/2 1/4
quote # REPL[121], line 4:
x = 1 # REPL[121], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end
julia> eval(ans)
:(1 / 2 + 1 / 4)
julia> @makeex5 y=1 $y $y
ERROR: UndefVarError: y not defined
Things started well, but something has gone wrong. The macro's generated code is trying to interpolate the copy of y
in the macro invocation scope; but there is no copy of y
in the macro invocation scope. Our error is allowing interpolation with the second and third arguments in the macro. To fix this error, we must use QuoteNode
.
julia> macro makeex6(expr, left, right)
quote
quote
$$(Meta.quot(expr))
:($$(Meta.quot($(QuoteNode(left)))) + $$(Meta.quot($(QuoteNode(right)))))
end
end
end
@makeex6 (macro with 1 method)
julia> @makeex6 y=1 1/2 1/4
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end
julia> eval(ans)
:(1 / 2 + 1 / 4)
julia> @makeex6 y=1 $y $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> eval(ans)
:(1 + 1)
julia> @makeex6 y=1 1+$y $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 + $(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> @makeex6 y=1 $y/2 $y
quote # REPL[129], line 4:
y = 1 # REPL[129], line 5:
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)) / 2)))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end
julia> eval(ans)
:(1 / 2 + 1)
By using QuoteNode
, we have protected our arguments from interpolation. Since QuoteNode
only has the effect of additional protections, it is never harmful to use QuoteNode
, unless you desire interpolation. However, understanding the difference makes it possible to understand where and why Meta.quot
could be a better choice.
This long exercise is with an example that is plainly too complex to show up in any reasonable application. Therefore, we have justified the following rule of thumb, mentioned earlier:
Meta.quot
;QuoteNode
.Expr(:quote, x)
is equivalent to Meta.quot(x)
. However, the latter is more idiomatic and is preferred. For code that heavily uses metaprogramming, a using Base.Meta
line is often used, which allows Meta.quot
to be referred to as simply quot
.