Julia Language
metaprogrammering
Sök…
Syntax
- makronamn (ex) ... slut
- offert ... slut
- : (...)
- $ x
- Meta.quot (x)
- QuoteNode (x)
- esc (x)
Anmärkningar
Julias metaprogrammeringsfunktioner är starkt inspirerade av de från Lisp-liknande språk och kommer att verka bekanta för dem med lite Lisp-bakgrund. Metaprogrammering är mycket kraftfull. När den används korrekt kan det leda till en mer kortfattad och läsbar kod.
quote ... end
är kvasakvotsyntax. I stället för uttryck som utvärderas, analyseras de helt enkelt. Värdet på quote ... end
slututtrycket är det resulterande Abstract Syntax Tree (AST).
Syntax :(...)
liknar quote ... end
syntax, men det är mer lätt. Denna syntax är mer kortfattad än quote ... end
.
Inuti en kvasakvot är $
-operatören speciell och interpolerar sitt argument i AST. Argumentet förväntas vara ett uttryck som är skarvad direkt i AST.
Meta.quot(x)
citerar sitt argument. Detta är ofta användbart i kombination med att använda $
för interpolering, eftersom det gör att uttryck och symboler kan delas bokstavligen i AST.
Återimplementera @show-makroen
I Julia är @show
ofta användbar för felsökning. Det visar både uttrycket som ska utvärderas och dess resultat och slutligen returnerar värdet på resultatet:
julia> @show 1 + 1
1 + 1 = 2
2
Det är enkelt att skapa vår egen version av @show
:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
För att använda den nya versionen använder du bara @myshow
:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
Tills slingan
Vi är alla vana vid while
syntax, som kör sin kropp medan villkoret utvärderas till true
. Tänk om vi vill implementera en until
loop, som kör en slinga tills villkoret utvärderas till true
?
I Julia kan vi göra detta genom att skapa ett @until
makro som slutar att köra sin kropp när villkoret är uppfyllt:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
Här har vi använt funktionen chaining syntax |>
, vilket motsvarar esc
funktionen på hela quote
. esc
funktionen förhindrar makrohygien från att applicera på makroinnehållet; utan det kommer variabler som skopas i makroen att bytas namn för att förhindra kollisioner med externa variabler. Se Julia-dokumentationen om makrohygien för mer information.
Du kan använda mer än ett uttryck i den här slingan genom att helt enkelt sätta allt i ett begin ... end
slutblock:
julia> i = 0; julia> @until i == 10 begin println(i) i += 1 end 0 1 2 3 4 5 6 7 8 9 julia> i 10
QuoteNode, Meta.quot och Expr (: offert)
Det finns tre sätt att citera något med en Julia-funktion:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
Vad betyder "citationstecken" och vad är det bra för? Citering gör att vi kan skydda uttryck från att tolkas som specialformer av Julia. Ett vanligt fall är när vi genererar uttryck som bör innehålla saker som utvärderas till symboler. (Till exempel måste detta makro returnera ett uttryck som utvärderas till en symbol.) Det fungerar inte bara för att returnera symbolen:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
Vad händer här? @mysym
expanderar till :x
, som som ett uttryck tolkas som variabeln x
. Men inget har tilldelats x
ännu, så vi får ett fel som x not defined
.
För att komma runt detta måste vi citera resultatet av vår makro:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Här har vi använt Meta.quot
funktionen för att förvandla vår symbol till en citerad symbol, vilket är det resultat vi vill ha.
Vad är skillnaden mellan Meta.quot
och QuoteNode
, och vilken ska jag använda? I nästan alla fall spelar skillnaden ingen roll. Det är kanske lite säkrare ibland att använda QuoteNode
istället för Meta.quot
. Att utforska skillnaden är informativt hur Julia-uttryck och makron fungerar.
Skillnaden mellan Meta.quot
och QuoteNode
, förklaras
Här är en tumregel:
- Om du behöver eller vill stödja interpolering, använd
Meta.quot
; - Om du inte kan eller inte vill tillåta interpolering, använd
QuoteNode
.
Kort sagt är skillnaden att Meta.quot
tillåter interpolering inom den citerade saken, medan QuoteNode
skyddar sitt argument från alla interpolationer. För att förstå interpolering är det viktigt att nämna $
-uttrycket. Det finns ett slags uttryck i Julia som kallas ett $
-uttryck. Dessa uttryck möjliggör rymning. Tänk till exempel på följande uttryck:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
När det utvärderas kommer detta uttryck att utvärdera 1
och tilldela det till x
, sedan konstruera ett uttryck med formen _ + _
där _
kommer att ersättas med värdet på x
. Således skulle resultatet av detta vara uttrycket 1 + 1
(som ännu inte utvärderas och så skiljer sig från värdet 2
). Detta är faktiskt fallet:
julia> eval(ex)
:(1 + 1)
Låt oss säga nu att vi skriver ett makro för att bygga upp sådana uttryck. Vår makro kommer att ta ett argument som kommer att ersätta 1
i ex
ovan. Detta argument kan naturligtvis vara vilket som helst uttryck. Här är något som vi inte vill:
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
Det andra fallet är felaktigt, eftersom vi borde hålla 1 + 1
obevärderade. Vi fixar det genom att citera argumentet med 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
Makrohygien gäller inte innehållet i en offert, så det är inte nödvändigt att rymma i detta fall (och i själva verket inte lagligt) i det här fallet.
Som nämnts tidigare tillåter Meta.quot
interpolering. Så låt oss prova det:
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
Från det första exemplet ser vi att interpolering tillåter oss att inline sin(1)
, istället för att uttrycket är en bokstavlig sin(1)
. Det andra exemplet visar att denna interpolering sker inom makroinkallationsomfånget, inte makroens eget räckvidd. Det beror på att vår makro faktiskt inte har utvärderat någon kod; allt det gör är att generera kod. Utvärderingen av koden (som tar sig in i uttrycket) görs när uttrycket som makroen genererar verkligen körs.
Tänk om vi hade använt QuoteNode
istället? Som ni kan gissa, eftersom QuoteNode
förhindrar att interpolering sker alls, betyder det att det inte fungerar.
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
I det här exemplet kan vi komma överens om att Meta.quot
ger större flexibilitet eftersom det tillåter interpolering. Så varför kan vi någonsin överväga att använda QuoteNode
? I vissa fall kanske vi faktiskt inte önskar interpolering, och vi vill faktiskt ha det bokstavliga $
-uttrycket. När skulle det vara önskvärt? Låt oss överväga en generalisering av @makeex
där vi kan överföra ytterligare argument som avgör vad som kommer till vänster och höger om +
-tecknet:
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)
En begränsning av vår implementering av @makeex4
är att vi inte kan använda uttryck som antingen vänster och höger sida av uttrycket direkt, eftersom de interpoleras. Med andra ord kan uttrycka utvärderas för interpolering, men vi kanske vill att de ska bevaras. (Eftersom det finns många nivåer av citat och utvärdering här, låt oss förtydliga: vår makro genererar kod som konstruerar ett uttryck som vid utvärdering ger ett annat uttryck . 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)
Vi borde låta användaren specificera när interpolering ska ske och när det inte borde göra det. Teoretiskt sett är det en enkel lösning: vi kan bara ta bort ett av $
-tecknen i vår applikation och låta användaren bidra med sina egna. Vad detta betyder är att vi interpolerar en citerad version av uttrycket som användaren har angett (som vi redan citerat och interpolerat en gång). Detta leder till följande kod, som kan vara lite förvirrande till en början, på grund av de många kapslade nivåerna för citationstecken och uppräkning. Försök att läsa och förstå vad varje flykt är till för.
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
Saker började bra, men något har gått fel. Makroens genererade kod försöker interpolera kopian av y
i makroinkallationsomfånget; men det finns ingen kopia av y
i makroinkallationsomfånget. Vårt fel är att tillåta interpolering med det andra och det tredje argumentet i makroen. För att fixa detta fel måste vi använda 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)
Genom att använda QuoteNode
har vi skyddat våra argument från interpolering. Eftersom QuoteNode
endast har effekten av ytterligare skydd är det aldrig skadligt att använda QuoteNode
, såvida du inte vill ha interpolering. Att förstå skillnaden gör det dock möjligt att förstå var och varför Meta.quot
kan vara ett bättre val.
Denna långa övning är med ett exempel som är helt klart för komplex för att dyka upp i alla rimliga tillämpningar. Därför har vi rättfärdigat följande tumregel, som nämnts tidigare:
- Om du behöver eller vill stödja interpolering, använd
Meta.quot
; - Om du inte kan eller inte vill tillåta interpolering, använd
QuoteNode
.
Vad sägs om Expr (: offert)?
Expr(:quote, x)
motsvarar Meta.quot(x)
. Den senare är emellertid mer idiomatisk och föredras. För kod som i using Base.Meta
grad använder metaprogrammering används ofta en using Base.Meta
linje som gör det Meta.quot
att hänvisa till quot
som helt enkelt quot
.
Guide
π: s Metaprogrammeringsbitar & bobs
mål:
Lär dig genom minimala målinriktade funktionella / användbara / icke-abstrakta exempel (t.ex.
@swap
eller@assert
) som introducerar begrepp i lämpliga sammanhangFöredra att låta koden illustrera / demonstrera begreppen snarare än förklarande stycken
Undvik att länka "obligatorisk läsning" till andra sidor - det avbryter berättelsen
Presentera saker i en förnuftig ordning som underlättar lärandet
Resurser:
julialang.org
wikibook (@Cormullion)
5 lager (Leah Hanson)
SO-Doc-offert (@TotalVerb)
SO-Doc - Symboler som inte är juridiska identifierare (@TotalVerb)
SO: Vad är en symbol i Julia (@StefanKarpinski)
Diskurs tråd (@ pi-) Metaprogrammering
Det mesta av materialet har kommit från diskurskanalen, det mesta har kommit från fcard ... snälla tacka mig om jag hade glömt attribut.
Symbol
julia> mySymbol = Symbol("myName") # or 'identifier' :myName julia> myName = 42 42 julia> mySymbol |> eval # 'foo |> bar' puts output of 'foo' into 'bar', so 'bar(foo)' 42 julia> :( $mySymbol = 1 ) |> eval 1 julia> myName 1
Flytta flaggor till funktioner:
function dothing(flag) if flag == :thing_one println("did thing one") elseif flag == :thing_two println("did thing two") end end julia> dothing(:thing_one) did thing one julia> dothing(:thing_two) did thing two
Ett hashkey-exempel:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(Avancerat) (@fcard) :foo
aka :(foo)
ger en symbol om foo
är en giltig identifierare, annars ett uttryck.
# NOTE: Different use of ':' is: julia> :mySymbol = Symbol('hello world') #(You can create a symbol with any name with Symbol("<name>"), # which lets us create such gems as: julia> one_plus_one = Symbol("1 + 1") Symbol("1 + 1") julia> eval(one_plus_one) ERROR: UndefVarError: 1 + 1 not defined ... julia> valid_math = :($one_plus_one = 3) :(1 + 1 = 3) julia> one_plus_one_plus_two = :($one_plus_one + 2) :(1 + 1 + 2) julia> eval(quote $valid_math @show($one_plus_one_plus_two) end) 1 + 1 + 2 = 5 ...
I princip kan du behandla symboler som lätta strängar. Det är inte det de är för, men du kan göra det, så varför inte. Julias Base själv gör det, print_with_color(:red, "abc")
skriver ut en rödfärgad abc.
Expr (AST)
(Nästan) allt i Julia är ett uttryck, dvs ett exempel på Expr
, som kommer att innehålla en AST .
# when you type ... julia> 1+1 2 # Julia is doing: eval(parse("1+1")) # i.e. First it parses the string "1+1" into an `Expr` object ... julia> ast = parse("1+1") :(1 + 1) # ... which it then evaluates: julia> eval(ast) 2 # An Expr instance holds an AST (Abstract Syntax Tree). Let's look at it: julia> dump(ast) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Int64 1 typ: Any # TRY: fieldnames(typeof(ast)) julia> :(a + b*c + 1) == parse("a + b*c + 1") == Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1) true
Nesting Expr
s:
julia> dump( :(1+2/3) ) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol / 2: Int64 2 3: Int64 3 typ: Any typ: Any # Tidier rep'n using s-expr julia> Meta.show_sexpr( :(1+2/3) ) (:call, :+, 1, (:call, :/, 2, 3))
multiline Expr
s med quote
julia> blk = quote x=10 x+1 end quote # REPL[121], line 2: x = 10 # REPL[121], line 3: x + 1 end julia> blk == :( begin x=10; x+1 end ) true # Note: contains debug info: julia> Meta.show_sexpr(blk) (:block, (:line, 2, Symbol("REPL[121]")), (:(=), :x, 10), (:line, 3, Symbol("REPL[121]")), (:call, :+, :x, 1) ) # ... unlike: julia> noDbg = :( x=10; x+1 ) quote x = 10 x + 1 end
... så quote
är funktionellt samma men ger extra felsökningsinfo.
(*) TIPS : Använd let
att hålla x
inom blocket
quote
en quote
Expr(:quote, x)
används för att representera offerter inom offert.
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
liknar Expr(:quote, x)
men det förhindrar interpolering.
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( Ta reda på de olika citatmekanismerna i Julia metaprogrammering
Är $ och : (…) på något sätt inverser av varandra?
:(foo)
betyder "inte titta på värdet, titta på uttrycket" $foo
betyder "ändra uttrycket till dess värde"
:($(foo)) == foo
. $(:(foo))
är ett fel. $(...)
är inte en operation och gör inget av sig själv, det är en "interpolera detta!" tecken på att den citerande syntaxen använder. dvs det finns bara inom en offert.
Är $
foo
samma sak som eval(
foo
)
?
Nej! $foo
byts mot kompileringstidsvärde som eval(foo)
att göra det vid körning
eval
kommer att inträffa i den globala omfattningen interpolering är lokal
eval(:<expr>)
ska returnera samma sak som bara <expr>
(förutsatt att <expr>
är ett giltigt uttryck i det nuvarande globala utrymmet)
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
s
Redo? :)
# let's try to make this! julia> x = 5; @show x; x = 5
Låt oss skapa vår egen @show
makro:
macro log(x) :( println( "Expression: ", $(string(x)), " has value: ", $x ) ) end u = 42 f = x -> x^2 @log(u) # Expression: u has value: 42 @log(42) # Expression: 42 has value: 42 @log(f(42)) # Expression: f(42) has value: 1764 @log(:u) # Expression: :u has value: u
expand
att sänka ett Expr
5 lager (Leah Hanson) <- förklarar hur Julia tar källkoden som en sträng, tokeniserar den till en Expr
tre (AST), expanderar ut alla makron (fortfarande AST), sänker (sänks AST) och konverterar sedan till LLVM (och utöver - för tillfället behöver vi inte oroa oss för vad som ligger längre bort!)
F: code_lowered
fungerar på funktioner. Är det möjligt att sänka en Expr
? A: yup!
# function -> lowered-AST julia> code_lowered(*,(String,String)) 1-element Array{LambdaInfo,1}: LambdaInfo template for *(s1::AbstractString, ss::AbstractString...) at strings/basic.jl:84 # Expr(i.e. AST) -> lowered-AST julia> expand(:(x ? y : z)) :(begin unless x goto 3 return y 3: return z end) julia> expand(:(y .= x.(i))) :((Base.broadcast!)(x,y,i)) # 'Execute' AST or lowered-AST julia> eval(ast)
Om du bara vill utöka makron kan du använda macroexpand
:
# AST -> (still nonlowered-)AST but with macros expanded: julia> macroexpand(:(@show x)) quote (Base.println)("x = ",(Base.repr)(begin # show.jl, line 229: #28#value = x end)) #28#value end
... som returnerar en icke-sänkt AST men med alla makron utvidgade.
esc()
esc(x)
returnerar en Expr som säger "inte tillämpa hygien för detta", det är samma som Expr(:escape, x)
. Hygien är det som håller en makro fristående, och du esc
saker om du vill att de ska "läcka". t.ex
Exempel: swap
makro för att illustrera esc()
macro swap(p, q) quote tmp = $(esc(p)) $(esc(p)) = $(esc(q)) $(esc(q)) = tmp end end x,y = 1,2 @swap(x,y) println(x,y) # 2 1
$
tillåter oss att fly från quote
. Så varför inte bara $p
och $q
? dvs.
# FAIL! tmp = $p $p = $q $q = tmp
Eftersom det först skulle se till macro
för p
, och det skulle hitta en lokal p
dvs. parametern p
(ja, om du senare går till p
utan att esc
, betraktar makro p
parametern som en lokal variabel).
Så $p = ...
är bara en tilldelning till den lokala p
. det påverkar inte vilken variabel som har skickats in i samtalssammanhanget.
Ok så hur är det med:
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
Så esc(p)
"läcker" p
in den samtalskontext. "Det som överfördes till makro som vi får som p
"
julia> macro swap(p, q) quote tmp = $p $(esc(p)) = $q $(esc(q)) = tmp end end @swap (macro with 1 method) julia> x, y = 1, 2 (1,2) julia> @swap(x, y); julia> @show(x, y); x = 2 y = 1 julia> macroexpand(:(@swap(x, y))) quote # REPL[34], line 3: #10#tmp = x # REPL[34], line 4: x = y # REPL[34], line 5: y = #10#tmp end
Som ni ser tmp
får hygienbehandlingen #10#tmp
, medan x
och y
inte gör det. Julia skapar en unik identifierare för tmp
, något du manuellt kan göra med gensym
, dvs.
julia> gensym(:tmp) Symbol("##tmp#270")
Men: Det finns en gotcha:
julia> module Swap export @swap macro swap(p, q) quote tmp = $p $(esc(p)) = $q $(esc(q)) = tmp end end end Swap julia> using Swap julia> x,y = 1,2 (1,2) julia> @swap(x,y) ERROR: UndefVarError: x not defined
En annan sak som julias makrohygien gör är om makroen kommer från en annan modul, den gör alla variabler (som inte tilldelades inuti makroens återkommande uttryck, som tmp
i detta fall) globaler i den aktuella modulen, så $p
blir Swap.$p
, likaså $q
-> Swap.$q
.
I allmänhet, om du behöver en variabel som ligger utanför makroens räckvidd bör du undvika den, så du bör esc(p)
och esc(q)
oavsett om de är på LHS eller RHS i ett uttryck, eller ens ensamma.
människor har redan nämnt gensym
sa några gånger och snart kommer du att förföras av den mörka sidan av att inte slippa hela uttrycket med några få gensym
peppade här och där, men ... Se till att förstå hur hygien fungerar innan du försöker vara smartare än det! Det är inte en särskilt komplex algoritm så det borde inte ta för lång tid, men rusa inte! Använd inte den kraften förrän du förstår alla konsekvenserna av den ... (@fcard)
Exempel: until
makro
(@ Ismael-VC)
"until loop" macro until(condition, block) quote while ! $condition $block end end |> esc end julia> i=1; @until( i==5, begin; print(i); i+=1; end ) 1234
(@fcard) |>
är emellertid kontroversiell. Jag är förvånad över att en folkmassa inte har kommit att argumentera ännu. (kanske alla är bara trötta på det). Det finns en rekommendation att ha de flesta om inte alla makroerna bara vara ett samtal till en funktion, så:
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
... är ett säkrare alternativ.
## @ fcards enkla makroutmaning
Uppgift: Byt operander, så att swaps(1/2)
ger 2.00
dvs. 2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
Fler makroutmaningar från @fcard här
Interpolering och assert
makro
http://docs.julialang.org/en/release-0.5/manual/metaprogramming/#building-an-advanced-macro
macro assert(ex) return :( $ex ? nothing : throw(AssertionError($(string(ex)))) ) end
F: Varför de sista $
? S: Det interpoleras, dvs tvingar Julia att eval
den string(ex)
när exekvering passerar påkallandet av detta makro. dvs. om du bara kör den koden kommer den inte att tvinga någon utvärdering. Men i det ögonblick du assert(foo)
kommer Julia att åberopa detta makro och ersätta sitt 'AST-token / Expr' med vad det än returnerar, och $
kommer att starta.
En rolig hack för att använda {} för block
(@fcard) Jag tror inte att det är något tekniskt hålla {}
från att användas som block, i själva verket kan man även vits på resterande {}
syntax för att få det att fungera:
julia> macro c(block) @assert block.head == :cell1d esc(quote $(block.args...) end) end @c (macro with 1 method) julia> @c { print(1) print(2) 1+2 } 123
* (sannolikt att det fortfarande inte fungerar om / när {} syntaxen återanvändas)
Så först ser Julia makrotoken, så kommer den att läsa / analysera symboler tills matchningen end
, och skapa vad? En Expr
med .head=:macro
eller något? Lagrar den "a+1"
som en sträng eller delar den upp i :+(:a, 1)
? Hur ser du på?
?
(@fcard) I det här fallet på grund av lexikalisk omfattning är a odefinierat i @M
s räckvidd så det använder den globala variabeln ... Jag glömde faktiskt att undkomma flipplins uttryck i mitt dumma exempel, men "fungerar bara inom samma modul " del av det gäller fortfarande.
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
Anledningen är att om makroen används i någon annan modul än den som den definierades i, behandlas alla variabler som inte definieras i koden som ska utvidgas som globalt i makromodulen.
julia> macroexpand(:(M.@m)) :(M.a + 1)
AVANCERAD
### @ Ismael-VC
@eval begin "do-until loop" macro $(:do)(block, until::Symbol, condition) until ≠ :until && error("@do expected `until` got `$until`") quote let $block @until $condition begin $block end end end |> esc end end julia> i = 0 0 julia> @do begin @show i i += 1 end until i == 5 i = 0 i = 1 i = 2 i = 3 i = 4
Scotts makro:
"""
Internal function to return captured line number information from AST
##Parameters
- a: Expression in the julia type Expr
##Return
- Line number in the file where the calling macro was invoked
"""
_lin(a::Expr) = a.args[2].args[1].args[1]
"""
Internal function to return captured file name information from AST
##Parameters
- a: Expression in the julia type Expr
##Return
- The name of the file where the macro was invoked
"""
_fil(a::Expr) = string(a.args[2].args[1].args[2])
"""
Internal function to determine if a symbol is a status code or variable
"""
function _is_status(sym::Symbol)
sym in (:OK, :WARNING, :ERROR) && return true
str = string(sym)
length(str) > 4 && (str[1:4] == "ERR_" || str[1:5] == "WARN_" || str[1:5] == "INFO_")
end
"""
Internal function to return captured error code from AST
##Parameters
- a: Expression in the julia type Expr
##Return
- Error code from the captured info in the AST from the calling macro
"""
_err(a::Expr) =
(sym = a.args[2].args[2] ; _is_status(sym) ? Expr(:., :Status, QuoteNode(sym)) : sym)
"""
Internal function to produce a call to the log function based on the macro arguments and the AST from the ()->ERRCODE anonymous function definition used to capture error code, file name and line number where the macro is used
##Parameters
- level: Loglevel which has to be logged with macro
- a: Expression in the julia type Expr
- msgs: Optional message
##Return
- Statuscode
"""
function _log(level, a, msgs)
if isempty(msgs)
:( log($level, $(esc(:Symbol))($(_fil(a))), $(_lin(a)), $(_err(a)) )
else
:( log($level, $(esc(:Symbol))($(_fil(a))), $(_lin(a)), $(_err(a)), message=$(esc(msgs[1]))) )
end
end
macro warn(a, msgs...) ; _log(Warning, a, msgs) ; end
skräp / obearbetad ...
visa / dumpa ett makro
(@ pi-) Antag att jag bara gör macro m(); a+1; end
i ett nytt REPL. Utan a
definierad. Hur kan jag "visa" det? är det något sätt att "dumpa" ett makro? Utan att verkligen utföra det
(@fcard) Alla koder i makron läggs faktiskt in i funktioner, så att du bara kan se deras sänkta eller typindelade kod.
julia> macro m() a+1 end @m (macro with 1 method) julia> @code_typed @m LambdaInfo for @m() :(begin return Main.a + 1 end) julia> @code_lowered @m CodeInfo(:(begin nothing return Main.a + 1 end)) # ^ or: code_lowered(eval(Symbol("@m")))[1] # ouf!
Andra sätt att få en makrofunktion:
julia> macro getmacro(call) call.args[1] end @getmacro (macro with 1 method) julia> getmacro(name) = getfield(current_module(), name.args[1]) getmacro (generic function with 1 method) julia> @getmacro @m @m (macro with 1 method) julia> getmacro(:@m) @m (macro with 1 method)
julia> eval(Symbol("@M")) @M (macro with 1 method) julia> dump( eval(Symbol("@M")) ) @M (function of type #@M) julia> code_typed( eval(Symbol("@M")) ) 1-element Array{Any,1}: LambdaInfo for @M() julia> code_typed( eval(Symbol("@M")) )[1] LambdaInfo for @M() :(begin return $(Expr(:copyast, :($(QuoteNode(:(a + 1)))))) end::Expr) julia> @code_typed @M LambdaInfo for @M() :(begin return $(Expr(:copyast, :($(QuoteNode(:(a + 1)))))) end::Expr)
^ ser ut som om jag kan använda code_typed
istället
Hur förstår eval(Symbol("@M"))
?
(@fcard) För närvarande har varje makro en funktion associerad med den. Om du har ett makro som heter M
kallas @M
. Generellt kan du få en funktionsvärde med t.ex. eval(:print)
men med en makrofunktion måste du göra Symbol("@M")
, eftersom bara :@M
blir en Expr(:macrocall, Symbol("@M"))
och utvärdering som orsakar en makro-expansion.
Varför visar inte code_typed
typparam?
(@pi-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ här ser jag en ::Any
parametrar, men det verkar inte vara kopplat till token x
.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^ på liknande sätt här; det finns inget att ansluta io
till ::IO
Så detta kan verkligen inte vara en fullständig dumpning av AST-representationen för den specifika print
...?
(@fcard) print(::IO, ::Char)
berättar bara vilken metod det är, det är inte en del av AST. Det är inte ens närvarande i master längre:
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) Jag förstår inte vad du menar med det. Det verkar dumpa AST för den metodens kropp, nej? Jag trodde code_typed
ger AST för en funktion. Men det verkar sakna det första steget, dvs att ställa in symboler för params.
(@fcard) code_typed
är enbart avsedd att visa kroppens AST, men för tillfället ger det den kompletta AST för metoden, i form av en LambdaInfo
(0.5) eller CodeInfo
(0.6), men mycket av informationen utelämnas när det skrivs ut till svaret. Du måste inspektera LambdaInfo
fältet efter fält för att få alla detaljer. dump
kommer att översvämma ditt svar, så du kan prova:
macro method_info(call) quote method = @code_typed $(esc(call)) print_info_fields(method) end end function print_info_fields(method) for field in fieldnames(typeof(method)) if isdefined(method, field) && !(field in [Symbol(""), :code]) println(" $field = ", getfield(method, field)) end end display(method) end print_info_fields(x::Pair) = print_info_fields(x[1])
Som ger alla värden för de namngivna fälten i metodens AST:
julia> @method_info print(STDOUT, 'a') rettype = Void sparam_syms = svec() sparam_vals = svec() specTypes = Tuple{Base.#print,Base.TTY,Char} slottypes = Any[Base.#print,Base.TTY,Char] ssavaluetypes = Any[] slotnames = Any[Symbol("#self#"),:io,:c] slotflags = UInt8[0x00,0x00,0x00] def = print(io::IO, c::Char) at char.jl:45 nargs = 3 isva = false inferred = true pure = false inlineable = true inInference = false inCompile = false jlcall_api = 0 fptr = Ptr{Void} @0x00007f7a7e96ce10 LambdaInfo for print(::Base.TTY, ::Char) :(begin $(Expr(:invoke, LambdaInfo for write(::Base.TTY, ::Char), :(Base.write), :(io), :(c))) return Base.nothing end::Void)
Se lil ' def = print(io::IO, c::Char)
? Varsågod! (även slotnames = [..., :io, :c]
del) Också ja, skillnaden i output beror på att jag visade resultaten på master.
???
(@ Ismael-VC) menar du så här? Generisk leverans med symboler
Du kan göra det på detta sätt:
julia> function dispatchtest{alg}(::Type{Val{alg}}) println("This is the generic dispatch. The algorithm is $alg") end dispatchtest (generic function with 1 method) julia> dispatchtest(alg::Symbol) = dispatchtest(Val{alg}) dispatchtest (generic function with 2 methods) julia> function dispatchtest(::Type{Val{:Euler}}) println("This is for the Euler algorithm!") end dispatchtest (generic function with 3 methods) julia> dispatchtest(:Foo) This is the generic dispatch. The algorithm is Foo julia> dispatchtest(:Euler)
Detta är för Euler-algoritmen! Jag undrar vad tycker @fcard om generiska symbolsändningar! --- ^: ängel:
Modul Gotcha
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
Mer exakt fungerar bara inom toppnivån för den modul som makroen definierades i.
julia> module M macro m1() a+1 end end M julia> macro m2() a+1 end @m2 (macro with 1 method) julia> a = 1 1 julia> M.@m1 ERROR: UndefVarError: a not defined julia> @m2 2 julia> let a = 20 @m2 end 2
esc
att detta händer, men att alltid använda det strider mot språkdesignen. Ett bra försvar för detta är att hindra en från att använda och införa namn i makron, vilket gör dem svåra att spåra till en mänsklig läsare.
Python `dict` / JSON gillar syntax för` Dict`-bokstäver.
Introduktion
Julia använder följande syntax för ordböcker:
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
Medan Python och JSON ser ut så här:
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
För illustrativa syften kan vi också använda denna syntax i Julia och lägga till ny semantik till den ( Dict
syntax är det idiomatiska sättet i Julia, vilket rekommenderas).
Låt oss först se vilken typ av uttryck det är:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
Det betyder att vi måste ta detta :cell1d
uttryck och antingen omvandla det eller returnera ett nytt uttryck som ska se ut så här:
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
Makro definition
Följande makro tillåter, även om det är enkelt, att demonstrera sådan kodgenerering och transformation:
macro dict(expr)
# Check the expression has the correct form:
if expr.head ≠ :cell1d || any(sub_expr.head ≠ :(:) for sub_expr ∈ expr.args)
error("syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`")
end
# Create empty `:Dict` expression which will be returned:
block = Expr(:call, :Dict) # :(Dict())
# Append `(key => value)` pairs to the block:
for pair in expr.args
k, v = pair.args
push!(block.args, :($k => $v))
end # :(Dict(k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ))
# Block is escaped so it can reach variables from it's calling scope:
return esc(block)
end
Låt oss kolla den resulterande makroutvidgningen:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
Användande
julia> @dict {"a": :b, 'c': 1, :d: 2.0}
Dict{Any,Any} with 3 entries:
"a" => :b
:d => 2.0
'c' => 1
julia> @dict {
"string": :b,
'c' : 1,
:symbol : π,
Function: print,
(1:10) : range(1, 10)
}
Dict{Any,Any} with 5 entries:
1:10 => 1:10
Function => print
"string" => :b
:symbol => π = 3.1415926535897...
'c' => 1
Det sista exemplet motsvarar exakt:
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
felaktig användning
julia> @dict {"one": 1, "two": 2, "three": 3, "four": 4, "five" => 5}
syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`
julia> @dict ["one": 1, "two": 2, "three": 3, "four": 4, "five" => 5]
syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`
Lägg märke till att Julia har andra användningsområden för kolon :
som sådan måste du radla in bokstavliga uttryck med parentes eller använda range
, till exempel.