Skip to content

Cvikli/JuliaMacroCheatSheet

Repository files navigation

Julia Macro CheatSheet

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!

Macro hygiene (aka: SCOPE management)

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.

Reducing redundancy

quote 2+3 end == :(begin 2+3 end)  # both preserve the linenumbers (verifiable with `dump(…)`)
:(2+3)                             # also similar but without FIRST Linenumber

Evaluation time

$ (expression interpolation) evaluates when the expression is constructed (at parse time)

:() or quoteend(Quotations) evaluates only when the expression is passed to eval at runtime.

Learning/repeating knowledge from tests

Note:

  • All test has included using Base.Meta: quot, QuoteNode.
  • Linenumbers are removed from the CheatSheet!

Case - Basic expressions:

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

Case - Global space basic expressions:

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
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)))

Case - Expression hygiene:

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)

Case - Medium expression:

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"

Case - Medium expression:

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"

Case - Basic:

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"

Case - Value interpolation:

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

Case - Advanced expression interpolation (note: @ip: interpolation, l: left, r: r):

@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)

Possible antipatterns:

  • 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)) where ex.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.)

Frequent mistakes:

  • $esc(…) instead of $(esc(…))

  • $QuoteNode(…) instead of $(QuoteNode(…))

Sources:

Need simplification

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:

This readme is generated by README_generator.jl]

About

Macro CheatSheet

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages