The whole file is wider on this screen: https://github.com/Cvikli/JuliaMacroCheatSheet/blob/main/README.md
For me https://docs.julialang.org/en/v1/manual/metaprogramming/ just couldn't make these things understand so I tried to be as short as possible to reach understanding in each point.
Please help us correct things and any simplification is welcomed, It is still a little bit too complicated I know, this have to be even shorter!
In short: Escape: = "Access the local scope from where the macro is called!"
In macro hygiene, each interpolated variable(VAR
) in the macro points to the module where the macro is defined (ex: Main.VAR
or MyModule.VAR
) instead of the local VAR
in the macro's calling scope.
a=1
macro wrong(); :($:a); end
let a=2; @wrong(); end # =1
eval(let a=2; :($a); end) # =2 (exactly the same like: (`let a=2; a; end`))
macro correct(); :($(esc(:a))); end
let a=2; @correct(); end # =2
eval(let a=2; :($(esc(:a))); end) # ERROR: syntax: invalid syntax (escape (outerref a))
BUT it generates new variable for the macro scope instead of the "local" scope. So eventually it doesn't see the outer scope variables in this case and believe this is the "new scope where the expression has to work".
a=1
macro ✖(ex); :($ex); end
macro ✓(ex); :($(esc(ex))); end # this works similarly: `:(:ey)`
eval( :(a=2)) # a=2
# eval( :($(esc(a=3)))) # ERROR: MethodError: no method matching esc(; b::Int64)
@✖ a=4 # a=2
@✓ a=5 # a=5
display(@macroexpand @✖ a=4) # :(var"#54#a" = 4)
display(@macroexpand @✓ a=5) # :(a = 5)
also
macro ✖(va, ex); :($va=$ex); end
macro ✓(va, ex); :($(esc(va))=$(esc(ex))); end
@✖ a 5
@✓ a 6
display(@macroexpand @✖ a 5) # :(var"#7#a" = 5)
display(@macroexpand @✓ a 6) # :(a = 6)
First time it works in the macro scope, so it shadows(gensym(:a)
) the variable, second time it creates :a
symbol into the scope as a variable. We need to use esc
to reach the local scope.
quote 2+3 end == :(begin 2+3 end) # both preserve the linenumbers (verifiable with `dump(…)`)
:(2+3) # also similar but without FIRST Linenumber
$
(expression interpolation) evaluates when the expression is constructed (at parse time)
:(
…)
or quote
… end
(Quotations) evaluates only when the expression is passed to eval at runtime.
Note:
- All test has included
using Base.Meta: quot, QuoteNode
. - Linenumbers are removed from the CheatSheet!
x=:p # Main.x
p=7 # Main.p
@macroexpand(@fn) |
@fn |
eval(@fn) |
eval(eval(@fn)) |
|
macro fn(); :x; end |
:(Main.x) |
:p |
7 |
7 |
macro fn(); :(x); end |
:(Main.x) |
:p |
7 |
7 |
macro fn(); quot(:x); end |
:(:x) |
:x |
:p |
7 |
macro fn(); QuoteNode(:x); end |
:(:x) |
:x |
:p |
7 |
macro fn(); :(:x); end |
:(:x) |
:x |
:p |
7 |
macro fn(); quote; :x; end end |
quote
:x
end |
:x |
:p |
7 |
x=:p # Main.x
p=7 # Main.p
@macroexpand(@fn) |
@fn |
eval(@fn) |
eval(eval(@fn)) |
|
macro fn(); :($x); end |
:(Main.p) |
7 |
7 |
7 |
macro fn(); :($(esc(x))); end |
:p |
7 |
7 |
7 |
macro fn(); quot(x); end |
:(:p) |
:p |
7 |
7 |
Case - Nested quote: https://docs.julialang.org/en/v1/manual/metaprogramming/#Nested-quote
n=:(1 + 2)
e |
eval(e) |
|
e=quote quote $n end end |
quote
$(Expr(:quote, quote
$(Expr(:$, :n))
end))
end |
quote
1 + 2
end |
e=quote quote $$n end end |
quote
$(Expr(:quote, quote
$(Expr(:$, :(1 + 2)))
end))
end |
quote
3
end |
e=quote quot($n) end |
quote
quot(1 + 2)
end |
:($(Expr(:quote, 3))) |
e=quote QuoteNode($n) end |
quote
QuoteNode(1 + 2)
end |
:($(QuoteNode(3))) |
ex=:ey # Main.ex
p=7 # Main.p
let p=3;
@dummy p^2;
end |
let p=3;
@macroexpand @dummy p^2;
end |
let p=3;
@dummy z=p^2;
end |
let p=3;
@macroexpand @dummy z=p^2;
end |
|
macro dummy(ex); return ex; end |
49 |
:(Main.p ^ 2) |
49 |
:(var"#7#z" = Main.p ^ 2) |
macro dummy(ex); return esc(ex); end |
9 |
:(p ^ 2) |
9 |
:(z = p ^ 2) |
ex=:ey # Main.ex
x=:p # Main.x
p=7 # Main.p
@macroexpand(@fn x) |
@fn x |
eval(@fn x) |
eval(eval(@fn x)) |
|
macro fn(ex); ex; end |
:(Main.x) |
:p |
7 |
7 |
macro fn(ex); :($ex); end |
:(Main.x) |
:p |
7 |
7 |
macro fn(ex); quote; $ex; end end |
quote
Main.x
end |
:p |
7 |
7 |
macro fn(ex); :($(esc(ex))); end |
:x |
:p |
7 |
7 |
macro fn(ex); quote; $(esc(ex)); end end |
quote
x
end |
:p |
7 |
7 |
macro fn(ex); quot(ex); end |
:(:x) |
:x |
:p |
7 |
macro fn(ex); QuoteNode(ex); end |
:(:x) |
:x |
:p |
7 |
macro fn(ex); :ex; end |
:(Main.ex) |
:ey |
:ez |
UndefVarError(:ez) |
macro fn(ex); :(ex); end |
:(Main.ex) |
:ey |
:ez |
UndefVarError(:ez) |
macro fn(ex); :($(:ex)); end |
:(Main.ex) |
:ey |
:ez |
UndefVarError(:ez) |
macro fn(ex); :(:ex); end |
:(:ex) |
:ex |
:ey |
:ez |
macro fn(ex); quot(:ex); end |
:(:ex) |
:ex |
:ey |
:ez |
macro fn(ex); QuoteNode(:ex); end |
:(:ex) |
:ex |
:ey |
:ez |
macro fn(ex); quote; :ex; end end |
quote
:ex
end |
:ex |
:ey |
:ez |
macro fn(ex); :(string(ex)); end |
:(Main.string(Main.ex)) |
"ey" |
"ey" |
"ey" |
macro fn(ex); :(string($ex)); end |
:(Main.string(Main.x)) |
"p" |
"p" |
"p" |
macro fn(ex); string(ex); end |
"x" |
"x" |
"x" |
"x" |
macro fn(ex); :($(string(ex))); end |
"x" |
"x" |
"x" |
"x" |
ex=:ey # Main.ex
p=7 # Main.p
@macroexpand(@fn z=p^2) |
@fn z=p^2 |
let p=3; @fn z=p^2; end |
eval(@fn z=p^2) |
eval(eval(@fn z=p^2)) |
|
macro fn(ex); ex; end |
:(var"#8#z" = Main.p ^ 2) |
49 |
49 |
49 |
49 |
macro fn(ex); :($ex); end |
:(var"#13#z" = Main.p ^ 2) |
49 |
49 |
49 |
49 |
macro fn(ex); quote; $ex; end end |
quote
var"#18#z" = Main.p ^ 2
end |
49 |
49 |
49 |
49 |
macro fn(ex); :($(esc(ex))); end |
:(z = p ^ 2) |
49 |
9 |
49 |
49 |
macro fn(ex); quote; $(esc(ex)); end end |
quote
z = p ^ 2
end |
49 |
9 |
49 |
49 |
macro fn(ex); quot(ex); end |
:($(Expr(:copyast, :($(QuoteNode(:(z = p ^ 2))))))) |
:(z = p ^ 2) |
:(z = p ^ 2) |
49 |
49 |
macro fn(ex); QuoteNode(ex); end |
:($(QuoteNode(:(z = p ^ 2)))) |
:(z = p ^ 2) |
:(z = p ^ 2) |
49 |
49 |
macro fn(ex); :ex; end |
:(Main.ex) |
:ey |
:ey |
:ez |
UndefVarError(:ez) |
macro fn(ex); :(ex); end |
:(Main.ex) |
:ey |
:ey |
:ez |
UndefVarError(:ez) |
macro fn(ex); :($(:ex)); end |
:(Main.ex) |
:ey |
:ey |
:ez |
UndefVarError(:ez) |
macro fn(ex); :(:ex); end |
:(:ex) |
:ex |
:ex |
:ey |
:ez |
macro fn(ex); quot(:ex); end |
:(:ex) |
:ex |
:ex |
:ey |
:ez |
macro fn(ex); QuoteNode(:ex); end |
:(:ex) |
:ex |
:ex |
:ey |
:ez |
macro fn(ex); quote; :ex; end end |
quote
:ex
end |
:ex |
:ex |
:ey |
:ez |
macro fn(ex); :(string(ex)); end |
:(Main.string(Main.ex)) |
"ey" |
"ey" |
"ey" |
"ey" |
macro fn(ex); :(string($ex)); end |
:(Main.string($(Expr(:(=), Symbol("#23#z"), :(Main.p ^ 2))))) |
"49" |
"49" |
"49" |
"49" |
macro fn(ex); string(ex); end |
"z = p ^ 2" |
"z = p ^ 2" |
"z = p ^ 2" |
"z = p ^ 2" |
"z = p ^ 2" |
macro fn(ex); :($(string(ex))); end |
"z = p ^ 2" |
"z = p ^ 2" |
"z = p ^ 2" |
"z = p ^ 2" |
"z = p ^ 2" |
ex=:ey # Main.ex
x=:p # Main.x
p=7 # Main.p
@fn x | ex |
:ex |
string(ex) |
:ey |
:(ey) |
$(ex) |
$(esc(ex)) |
$(string(ex)) |
string($ex) |
macro fn(ex)
…
end |
:p |
:ey |
"x" |
:ez |
:ez |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
macro fn(ex)
quot(…)
end |
:x |
:ex |
"x" |
:ey |
:ey |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
macro fn(ex)
QuoteNode(…)
end |
:x |
:ex |
"x" |
:ey |
:ey |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
macro fn(ex)
:(…)
end |
:ey |
:ex |
"ey" |
:ey |
:ey |
:p |
:p |
"x" |
"p" |
macro fn(ex)
quote
…
end
end |
:ey |
:ex |
"ey" |
:ey |
:ey |
:p |
:p |
"x" |
"p" |
q=:p # Main.q
p=7 # Main.p
@quo 2 |
@quo 2 + 2 |
@quo 2 + $(sin(1)) |
@quo 2 + $q |
eval(@quo 2 + $q) |
|
macro quo(ex)
:(x=$(esc(ex));
:($x+$x))
end |
:(2 + 2) |
:(4 + 4) |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
syntax: "$" expression outside quote |
macro quo(ex)
:(x=$(quot(ex));
:($x+$x))
end |
:(2 + 2) |
:((2 + 2) + (2 + 2)) |
:((2 + 0.8414709848078965) + (2 + 0.8414709848078965)) |
:((2 + p) + (2 + p)) |
18 |
macro quo(ex)
:(x=$(QuoteNode(ex));
:($x+$x))
end |
:(2 + 2) |
:((2 + 2) + (2 + 2)) |
:((2 + $(Expr(:$, :(sin(1))))) + (2 + $(Expr(:$, :(sin(1)))))) |
:((2 + $(Expr(:$, :q))) + (2 + $(Expr(:$, :q)))) |
syntax: "$" expression outside quote |
@ip y=1 y y |
eval(@ip y=1 y y) |
eval(eval(@ip y=1 y y)) |
@ip y=1 y/2 y |
eval(@ip y=1 y/2 y) |
@ip y=1 1/2 1/4 |
eval(@ip y=1 1/2 1/4) |
@ip y=1 $y $y |
eval(@ip y=1 1+$y $y) |
@ip y=1 $y/2 $y |
eval(@ip y=1 $y/2 $y) |
|
macro ip(ex, l, r)
quote
Meta.quot($ex)
:($$(Meta.quot(l)) + $$(Meta.quot(r)))
end
end |
:(y + y) |
MethodError: +(:p, :p) |
MethodError: +(:p, :p) |
:(y / 2 + y) |
MethodError: /(:p, 2) |
:(1 / 2 + 1 / 4) |
0.75 |
:(1 + 1) |
3 |
:(1 / 2 + 1) |
1.5 |
macro ip(ex, l, r)
quote
$(Meta.quot(ex))
:($$(Meta.quot(l)) + $$(Meta.quot(r)))
end
end |
:(y + y) |
MethodError: +(:p, :p) |
MethodError: +(:p, :p) |
:(y / 2 + y) |
MethodError: /(:p, 2) |
:(1 / 2 + 1 / 4) |
0.75 |
:(p + p) |
15 |
:(p / 2 + p) |
10.5 |
macro ip(ex, l, r)
quote
quote
$$(Meta.quot(ex))
:($$$(Meta.quot(l)) + $$$(Meta.quot(r)))
end
end
end |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :y)) + $(Expr(:$, :y)))))
end |
:(1 + 1) |
2 |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(y / 2))) + $(Expr(:$, :y)))))
end |
:(0.5 + 1) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(1 / 2))) + $(Expr(:$, :(1 / 4))))))
end |
:(0.5 + 0.25) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, 1)) + $(Expr(:$, 1)))))
end |
:(2 + 1) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(1 / 2))) + $(Expr(:$, 1)))))
end |
:(0.5 + 1) |
macro ip(ex, l, r)
quote
quote
$$(Meta.quot(ex))
:($$$(Meta.quot(l)) + $$$(Meta.quot(r)))
end
end
end |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :y)) + $(Expr(:$, :y)))))
end |
:(1 + 1) |
2 |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(y / 2))) + $(Expr(:$, :y)))))
end |
:(0.5 + 1) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(1 / 2))) + $(Expr(:$, :(1 / 4))))))
end |
:(0.5 + 0.25) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, 1)) + $(Expr(:$, 1)))))
end |
:(2 + 1) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(1 / 2))) + $(Expr(:$, 1)))))
end |
:(0.5 + 1) |
macro ip(ex, l, r)
quote
quote
$$(Meta.quot(ex))
:($$(Meta.quot($(Meta.quot(l)))) + $$(Meta.quot($(Meta.quot(r)))))
end
end
end |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(:y))) + $(Expr(:$, :(:y))))))
end |
:(y + y) |
2 |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(y / 2)))))) + $(Expr(:$, :(:y))))))
end |
:(y / 2 + y) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end |
:(1 / 2 + 1 / 4) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, 1))))) + $(Expr(:$, :($(Expr(:quote, 1))))))))
end |
:((1 + 1) + 1) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, 1))))))))
end |
:(1 / 2 + 1) |
macro ip(ex, l, r)
quote
quote
$$(Meta.quot(ex))
:($$(Meta.quot($(QuoteNode(l)))) + $$(Meta.quot($(QuoteNode(r)))))
end
end
end |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :(:y))) + $(Expr(:$, :(:y))))))
end |
:(y + y) |
2 |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(y / 2)))))) + $(Expr(:$, :(:y))))))
end |
:(y / 2 + y) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end |
:(1 / 2 + 1 / 4) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end |
:((1 + 1) + 1) |
quote
y = 1
$(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)) / 2)))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end |
:(1 / 2 + 1) |
- If you validate the
ex.head
, then using the function in a macro can lead to unusability due to escaping the expression to reach local scope. Because it is$(Expr(:escape, VAR))
whereex.head
==:escape
. Issue: JuliaLang/julia#37691 (So while this is an edge case we should be keep it in our mind if we want to create really universal macros.)
-
$esc(…)
instead of$(esc(…))
-
$QuoteNode(…)
instead of$(QuoteNode(…))
-
https://riptutorial.com/julia-lang/example/24364/quotenode--meta-quot--and-ex--quote-
-
https://nextjournal.com/a/KpqWNKDvNLnkBrgiasA35?change-id=CQRuZrWB1XaT71H92x8Y2Q
Section: https://docs.julialang.org/en/v1/manual/metaprogramming/#man-quote-node
Still total chaotic for me and cannot make a simple explanation. My weak explanation throught tests:
:( $:(1+2)) # :(1 + 2) note if it would be $n then of course the interpolation would be visible!
Expr(:$, :(1+2)) # :($(Expr(:$, :(1 + 2))))
quot(Expr(:$, :(1+2)) # :($(Expr(:quote, :($(Expr(:$, :(1 + 2)))))))
QuoteNode(Expr(:$, :(1+2)) # :($(QuoteNode(:($(Expr(:$, :(1 + 2)))))))
eval(Expr(:$, :(1+2)) # ERROR: syntax: "$" expression outside quote
eval(quot(Expr(:$, :(1+2))) # 3
eval(QuoteNode(Expr(:$, :(1+2))) # :($(Expr(:$, :(1 + 2))))
eval(eval(QuoteNode(Expr(:$, :(1+2)))) # 3
Unfinished but main doc is okay actually:
-
https://docs.julialang.org/en/v1/manual/metaprogramming/#meta-non-standard-string-literals
-
https://docs.julialang.org/en/v1/manual/metaprogramming/#Generated-functions