Julia Language
metaprogramming
Zoeken…
Syntaxis
- macro naam (ex) ... einde
- citaat ... einde
- : (...)
- $ x
- Meta.quot (x)
- QuoteNode (x)
- esc (x)
Opmerkingen
Julia's metaprogrammeringsfuncties zijn sterk geïnspireerd door die van Lisp-achtige talen en zullen bekend zijn voor mensen met een Lisp-achtergrond. Metaprogrammering is erg krachtig. Bij correct gebruik kan dit leiden tot meer beknopte en leesbare code.
Het quote ... end
is de syntaxis van quasiquote. In plaats van de uitdrukkingen die worden geëvalueerd, worden ze eenvoudigweg ontleed. De waarde van de quote ... end
expressie is de resulterende Abstract Syntax Tree (AST).
De :(...)
syntaxis lijkt op de quote ... end
syntaxis, maar is lichter. Deze syntaxis is beknopter dan quote ... end
.
Binnen een quasiquote, de $
operator is speciaal en interpoleert haar betoog in de AST. Verwacht wordt dat het argument een uitdrukking is die direct in de AST wordt gesplitst.
De functie Meta.quot(x)
citeert zijn argument. Dit is vaak handig in combinatie met het gebruik van $
voor interpolatie, omdat uitdrukkingen en symbolen letterlijk in de AST kunnen worden gesplitst.
De @show-macro opnieuw implementeren
In Julia is de macro @show
vaak handig voor het opsporen van fouten. Het toont zowel de te evalueren uitdrukking als het resultaat ervan, en retourneert uiteindelijk de waarde van het resultaat:
julia> @show 1 + 1
1 + 1 = 2
2
Het is eenvoudig om onze eigen versie van @show
:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
Om de nieuwe versie te gebruiken, gebruikt u eenvoudig de macro @myshow
:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
Tot lus
We zijn allemaal gewend aan de syntaxis van while
, die zijn lichaam uitvoert terwijl de voorwaarde als true
wordt geëvalueerd. Wat als we een until
lus willen implementeren, die een lus uitvoert totdat de voorwaarde als true
is geëvalueerd?
In Julia kunnen we dit doen door een @until
macro te maken, die stopt met het uitvoeren van zijn lichaam wanneer aan de voorwaarde is voldaan:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
Hier hebben we de functie chaining syntax |>
, wat gelijk staat aan het aanroepen van de esc
functie op het hele quote
blok. De esc
functie voorkomt dat esc
wordt toegepast op de inhoud van de macro; zonder dit zullen variabelen die in de macro zijn opgenomen een nieuwe naam krijgen om botsingen met externe variabelen te voorkomen. Raadpleeg de Julia-documentatie over macrohygiëne voor meer informatie.
Je kunt meer dan één uitdrukking in deze lus gebruiken, door eenvoudig alles in een begin ... end
eindblok te plaatsen:
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 en Expr (: quote)
Er zijn drie manieren om iets te citeren met behulp van een Julia-functie:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
Wat betekent "citeren" en waar is het goed voor? Met citeren kunnen we voorkomen dat uitdrukkingen door Julia als speciale vormen worden geïnterpreteerd. Een veelvoorkomend geval is wanneer we uitdrukkingen genereren die dingen moeten bevatten die evalueren naar symbolen. ( Deze macro moet bijvoorbeeld een uitdrukking retourneren die resulteert in een symbool.) Het werkt niet eenvoudig om het symbool terug te geven:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
Wat is hier aan de hand? @mysym
breidt uit naar :x
, dat als een uitdrukking wordt geïnterpreteerd als de variabele x
. Maar er is nog niets aan x
toegewezen, dus we krijgen een x not defined
foutmelding.
Om dit te omzeilen, moeten we het resultaat van onze macro citeren:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Hier hebben we de functie Meta.quot
gebruikt om van ons symbool een aanhalingsteken te maken, wat we willen.
Wat is het verschil tussen Meta.quot
en QuoteNode
en welke moet ik gebruiken? In bijna alle gevallen doet het verschil er niet echt toe. Het is misschien een beetje veiliger om QuoteNode
te gebruiken QuoteNode
plaats van Meta.quot
. Het verschil onderzoeken is echter informatief over hoe Julia-expressies en macro's werken.
Het verschil tussen Meta.quot
en QuoteNode
, uitgelegd
Hier is een vuistregel:
- Gebruik
Meta.quot
als u interpolatie nodig hebt of wilt ondersteunen; - Gebruik
QuoteNode
als u interpolatie niet kunt of wilt toestaan.
Kortom, het verschil is dat Meta.quot
interpolatie binnen het geciteerde ding QuoteNode
, terwijl QuoteNode
zijn argument beschermt tegen elke interpolatie. Om interpolatie te begrijpen, is het belangrijk om de $
-uitdrukking te vermelden. Er is een soort uitdrukking in Julia die een $
uitdrukking wordt genoemd. Deze uitdrukkingen laten ontsnappen toe. Overweeg bijvoorbeeld de volgende uitdrukking:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Wanneer geëvalueerd, evalueert deze uitdrukking 1
en wijst deze toe aan x
, en construeert vervolgens een uitdrukking in de vorm _ + _
waarbij de _
wordt vervangen door de waarde van x
. Het resultaat hiervan moet dus de uitdrukking 1 + 1
(die nog niet is geëvalueerd en zo verschilt van de waarde 2
). Dit is inderdaad het geval:
julia> eval(ex)
:(1 + 1)
Laten we zeggen dat we een macro schrijven om dit soort expressies te maken. Onze macro heeft een argument nodig, dat de 1
in de ex
hierboven zal vervangen. Dit argument kan natuurlijk elke uitdrukking zijn. Hier is iets dat niet helemaal is wat we willen:
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
Het tweede geval is onjuist, omdat we 1 + 1
geëvalueerd moeten houden. We lossen dat op door het argument met 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
Macrohygiëne is niet van toepassing op de inhoud van een offerte, dus in dit geval is ontsnappen niet nodig (en in dit geval zelfs niet legaal).
Zoals eerder vermeld, staat Meta.quot
interpolatie toe. Laten we dat eens proberen:
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
Uit het eerste voorbeeld zien we dat interpolatie ons in staat stelt om de sin(1)
te onderstrepen, in plaats van dat de uitdrukking een letterlijke sin(1)
. Het tweede voorbeeld laat zien dat deze interpolatie gebeurt in het macro-aanroepbereik, niet in het eigen bereik van de macro. Dat komt omdat onze macro geen code heeft geëvalueerd; alles wat het doet is code genereren. De evaluatie van de code (die zijn weg vindt naar de uitdrukking) wordt uitgevoerd wanneer de uitdrukking die de macro genereert daadwerkelijk wordt uitgevoerd.
Wat als we in plaats daarvan QuoteNode
hadden gebruikt? Zoals u misschien wel raadt, aangezien QuoteNode
interpolatie voorkomt, betekent dit dat het niet werkt.
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 dit voorbeeld kunnen we het erover eens zijn dat Meta.quot
meer flexibiliteit biedt, omdat het interpolatie mogelijk maakt. Dus waarom zouden we ooit overwegen om QuoteNode
? In sommige gevallen verlangen we misschien niet echt naar interpolatie en willen we eigenlijk de letterlijke uitdrukking $
. Wanneer zou dat wenselijk zijn? Laten we een generalisatie van @makeex
waar we aanvullende argumenten kunnen doorgeven die bepalen wat links en rechts van het +
-teken komt:
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)
Een beperking van onze implementatie van @makeex4
is dat we expressies niet als de linker- en rechterkant van de expressie kunnen gebruiken, omdat ze geïnterpoleerd worden. Met andere woorden, de expressies kunnen worden geëvalueerd voor interpolatie, maar we willen ze misschien behouden. (Aangezien er hier veel niveaus van citeren en evalueren zijn, laten we het verduidelijken: onze macro genereert code die een expressie construeert die na evaluatie een andere expressie produceert. Pff!)
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 moeten de gebruiker de mogelijkheid geven om te specificeren wanneer interpolatie moet plaatsvinden en wanneer niet. Theoretisch is dat een gemakkelijke oplossing: we kunnen gewoon een van de $
-tekens in onze applicatie verwijderen en de gebruiker zijn eigen bijdrage laten leveren. Dit betekent dat we een geciteerde versie van de uitdrukking die door de gebruiker is ingevoerd (die we al eerder hebben geciteerd en geïnterpoleerd) interpoleren. Dit leidt tot de volgende code, die in het begin wat verwarrend kan zijn vanwege de meerdere geneste niveaus van citeren en citeren. Probeer te lezen en te begrijpen waar elke ontsnapping voor is.
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
Het begon goed, maar er is iets misgegaan. De gegenereerde code van de macro probeert de kopie van y
in het macro-aanroepbereik te interpoleren; maar er is geen kopie van y
in het macro-aanroepbereik. Onze fout maakt interpolatie mogelijk met de tweede en derde argumenten in de macro. Om deze fout te verhelpen, moeten we 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)
Door QuoteNode
, hebben we onze argumenten beschermd tegen interpolatie. Omdat QuoteNode
alleen het effect van extra beveiligingen heeft, is het nooit schadelijk om QuoteNode
te gebruiken, tenzij u interpolatie wenst. Het begrip van het verschil maakt het echter mogelijk om te begrijpen waar en waarom Meta.quot
een betere keuze zou kunnen zijn.
Deze lange oefening is met een voorbeeld dat duidelijk te complex is om in een redelijke toepassing te verschijnen. Daarom hebben we de eerder genoemde vuistregel gerechtvaardigd:
- Gebruik
Meta.quot
als u interpolatie nodig hebt of wilt ondersteunen; - Gebruik
QuoteNode
als u interpolatie niet kunt of wilt toestaan.
Hoe zit het met Expr (: quote)?
Expr(:quote, x)
is gelijk aan Meta.quot(x)
. Dit laatste is echter idiomatischer en heeft de voorkeur. Voor code die veel metaprogrammering gebruikt, wordt vaak een regel using Base.Meta
gebruikt, waarmee Meta.quot
eenvoudigweg quot
kan worden genoemd.
Gids
π's metaprogrammering bits & bobs
doelen:
Leer door middel van minimale gerichte functionele / nuttige / niet-abstracte voorbeelden (bijv.
@swap
of@assert
) die concepten introduceren in geschikte contextenLaat de code liever de concepten illustreren / demonstreren in plaats van paragrafen met uitleg
Koppel 'verplicht lezen' niet aan andere pagina's - het onderbreekt het verhaal
Presenteer dingen in een verstandige volgorde die het leren het gemakkelijkst maakt
Middelen:
julialang.org
wikibook (@Cormullion)
5 lagen (Leah Hanson)
SO-Doc Quoting (@TotalVerb)
SO-Doc - Symbolen die geen wettelijke identificatie zijn (@TotalVerb)
SO: Wat is een symbool in Julia (@StefanKarpinski)
Discoursthread (@ pi-) Metaprogrammering
Het meeste materiaal is afkomstig van het discourskanaal, het meeste is afkomstig van fcard ... prik me alsjeblieft als ik attributies was vergeten.
Symbool
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
Vlaggen doorgeven aan functies:
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
Een hashkey-voorbeeld:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(Geavanceerd) (@fcard) :foo
aka :(foo)
levert een symbool op als foo
een geldige identificatie is, anders een uitdrukking.
# 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 ...
In principe kun je symbolen als lichtgewicht tekenreeksen behandelen. Daar zijn ze niet voor, maar u kunt het, dus waarom niet. Julia's Base doet het zelf, print_with_color(:red, "abc")
drukt een roodgekleurd abc af.
Uitdr (AST)
(Bijna) alles in Julia is een expressie, dat wil zeggen een instantie van Expr
, die een AST bevat .
# 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 met 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
... dus quote
is functioneel hetzelfde maar biedt extra debug-info.
(*) TIP : gebruik let
om x
binnen het blok te houden
quote
-ing een quote
Expr(:quote, x)
wordt gebruikt om aanhalingstekens binnen aanhalingstekens weer te geven.
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
lijkt op Expr(:quote, x)
maar het voorkomt interpolatie.
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( Onderscheid de verschillende citatiemechanismen in Julia metaprogrammering
Zijn $ en : (…) op de een of andere manier omgekeerd van elkaar?
:(foo)
betekent "kijk niet naar de waarde, kijk naar de uitdrukking" $foo
betekent "verander de uitdrukking in zijn waarde"
:($(foo)) == foo
. $(:(foo))
is een fout. $(...)
is geen operatie en doet zelf niets, het is een "interpoleer dit!" teken dat de citaatsyntaxis gebruikt. ie Het bestaat alleen binnen een offerte.
Is $
foo
hetzelfde als eval(
foo
)
?
Nee! $foo
wordt ingeruild voor de compile-time waarde eval(foo)
betekent om dat tijdens runtime te doen
eval
zal optreden in de globale reikwijdte interpolatie is lokaal
eval(:<expr>)
moet hetzelfde retourneren als alleen <expr>
(ervan uitgaande dat <expr>
een geldige uitdrukking is in de huidige globale ruimte)
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
s
Klaar? :)
# let's try to make this! julia> x = 5; @show x; x = 5
Laten we onze eigen @show
macro maken:
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
om een Expr
5 lagen (Leah Hanson) <- legt uit hoe Julia de broncode als een tekenreeks neemt, deze tokeniseert in een Expr
-tree (AST), alle macro's uitbreidt (nog steeds AST), verlaagt (verlaagde AST) en converteert vervolgens naar LLVM (en verder - op dit moment hoeven we ons geen zorgen te maken over wat daarachter ligt!)
V: code_lowered
werkt in op functies. Is het mogelijk om een Expr
te verlagen? A: jep!
# 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)
Als u alleen macro's wilt uitbreiden, kunt u 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
... die een niet-verlaagde AST retourneert, maar met alle macro's uitgebreid.
esc()
esc(x)
geeft een Uitdr terug die zegt: "Pas hier geen hygiëne op toe", dit is hetzelfde als Expr(:escape, x)
. Hygiëne is wat een macro op zichzelf houdt, en je esc
dingen als je wilt dat ze "lekken". bv
Voorbeeld: macro swap
om esc()
te illustreren
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
$
kunnen we het quote
'ontsnappen'. Dus waarom niet gewoon $p
en $q
? d.w.z
# FAIL! tmp = $p $p = $q $q = tmp
Omdat dat eerst naar het macro
voor p
zou kijken, en het zou een lokale p
dwz de parameter p
(ja, als u vervolgens p
zonder esc
toegang opent, beschouwt de macro de p
parameter als een lokale variabele).
Dus $p = ...
is gewoon een toewijzing aan de lokale p
. het heeft geen invloed op de variabele die in de aanroepcontext is doorgegeven.
Ok dus hoe zit het met:
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
Dus 'lek' p
esc(p)
p
in de aanroepende context. "Het ding dat werd doorgegeven in de macro die we ontvangen als 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
Zoals je kunt zien krijgt tmp
de hygiënische behandeling #10#tmp
, terwijl x
en y
dat niet doen. Julia maakt een unieke identificatie voor tmp
, iets wat je handmatig kunt doen met gensym
, namelijk:
julia> gensym(:tmp) Symbol("##tmp#270")
Maar: er is een 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
Iets anders wat julia's macrohygiëne doet is, als de macro uit een andere module komt, maakt het alle variabelen (die niet waren toegewezen in de terugkerende expressie van de macro, zoals in dit geval tmp
), globalen van de huidige module, dus $p
wordt Swap.$p
, eveneens $q
-> Swap.$q
.
Over het algemeen moet u esc als u een variabele nodig hebt die buiten het bereik van de macro valt, dus esc(p)
en esc(q)
ongeacht of deze zich op de LHS of RHS van een expressie bevinden, of zelfs alleen.
mensen hebben gensym
sa al een paar keer genoemd en binnenkort zul je verleid worden door de donkere kant van het verzuim om de hele uitdrukking te ontsnappen met een paar gensym
hier en daar doorspekt, maar ... Zorg ervoor dat je begrijpt hoe hygiëne werkt voordat je probeert slimmer dan het! Het is geen bijzonder complex algoritme, dus het moet niet te lang duren, maar haast je niet! Gebruik die kracht niet totdat je alle gevolgen ervan begrijpt ... (@fcard)
Voorbeeld: until
macro
(@ 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) |>
is echter controversieel. Het verbaast me dat een menigte nog niet is komen ruziën. (misschien is iedereen het gewoon beu). Er is een aanbeveling om de meeste, zo niet alle macro's slechts een aanroep van een functie te laten zijn, dus:
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
... is een veiliger alternatief.
## @ fcard's eenvoudige macro-uitdaging
Taak: Wissel de operanden om, zodat swaps(1/2)
2.00
geeft, dwz 2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
Meer macro-uitdagingen van @fcard hier
Interpolatie en assert
macro
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
Vraag: Waarom de laatste $
? A: Het interpoleert, dwz dwingt Julia om die string(ex)
te eval
wanneer de uitvoering de aanroep van deze macro passeert. dat wil zeggen als u die code uitvoert, zal deze geen evaluatie afdwingen. Maar op het moment dat je assert(foo)
zal Julia deze macro gebruiken en het 'AST-token / Expr' vervangen door wat het retourneert, en de $
zal in actie treden .
Een leuke hack voor het gebruik van {} voor blokken
(@fcard) Ik denk niet dat er iets technisch is waardoor {}
niet als blokken kan worden gebruikt, in feite kan men zelfs de resterende {}
syntaxis gebruiken om het te laten werken:
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
* (werkt waarschijnlijk nog steeds niet als / wanneer de syntaxis {} wordt hergebruikt)
Dus eerst ziet Julia het macro-token, dus het zal tokens lezen / ontleden tot het overeenkomende end
, en wat maken? Een Expr
met .head=:macro
of iets? Slaat het "a+1"
als een string of splitst het het op in :+(:a, 1)
? Hoe te bekijken?
?
(@fcard) In dit geval vanwege een lexicaal bereik, is a niet gedefinieerd in het @M
dus gebruikt het de globale variabele ... Ik vergat eigenlijk om te ontsnappen aan de flipplin-uitdrukking in mijn domme voorbeeld, maar de "werkt alleen binnen de dezelfde module ' is nog steeds van toepassing.
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
De reden hiervoor is dat, als de macro wordt gebruikt in een andere module dan de module waarin deze is gedefinieerd, alle variabelen die niet zijn gedefinieerd in de uit te breiden code worden behandeld als globalen van de macro's module.
julia> macroexpand(:(M.@m)) :(M.a + 1)
GEAVANCEERD
### @ 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
Scott's macro:
"""
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
ongewenste / onbewerkte ...
een macro bekijken / dumpen
(@ pi-) Stel dat ik gewoon macro m(); a+1; end
doe macro m(); a+1; end
in een nieuwe REPL. Zonder a
gedefinieerd. Hoe kan ik het 'bekijken'? zoals, is er een manier om een macro te 'dumpen'? Zonder het daadwerkelijk uit te voeren
(@fcard) Alle code in macro's worden in feite in functies geplaatst, zodat u alleen de verlaagde of door type afgeleide code kunt bekijken.
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!
Andere manieren om de functie van een macro te krijgen:
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)
^ lijkt erop dat ik in plaats daarvan code_typed
kan gebruiken
Hoe eval(Symbol("@M"))
te begrijpen?
(@fcard) Aan elke macro is momenteel een functie gekoppeld. Als u een macro met de naam M
, wordt de functie van de macro @M
. Over het algemeen kunt u de waarde van een functie verkrijgen met bijvoorbeeld eval(:print)
maar met de functie van een macro moet u Symbol("@M")
, omdat alleen :@M
een Expr(:macrocall, Symbol("@M"))
en evalueren dat een macro-uitbreiding veroorzaakt.
Waarom geeft code_typed
geen params weer?
(@pi-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ hier zie ik er een ::Any
parameter, maar deze lijkt niet te zijn verbonden met het token x
.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^ evenzo hier; er is niets dat io
verbindt met de ::IO
Dus dit kan toch geen complete dump zijn van de AST-weergave van die specifieke print
...?
(@fcard) print(::IO, ::Char)
vertelt je alleen welke methode het is, het maakt geen deel uit van de AST. Het is zelfs niet meer aanwezig in master:
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) Ik begrijp niet wat je daarmee bedoelt. Het lijkt de AST te dumpen voor het lichaam van die methode, niet? Ik dacht dat code_typed
de AST geeft voor een functie. Maar het lijkt de eerste stap te missen, namelijk het instellen van tokens voor params.
(@fcard) code_typed
is bedoeld om alleen de AST van het lichaam te tonen, maar voor nu geeft het wel de volledige AST van de methode, in de vorm van een LambdaInfo
(0,5) of CodeInfo
(0,6), maar veel informatie wordt weggelaten wanneer afgedrukt naar de repl. U moet het veld LambdaInfo
per veld inspecteren om alle details te krijgen. dump
zal je repl overspoelen, dus je zou kunnen proberen:
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])
Die alle waarden van de genoemde velden van de AST van een methode geeft:
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)
Zie de lil ' def = print(io::IO, c::Char)
? Daar ga je! (ook de slotnames = [..., :io, :c]
deel) Ook ja, het verschil in output is omdat ik de resultaten liet zien op master.
???
(@ Ismael-VC) bedoel je zo? Algemene verzending met symbolen
Je kunt het op deze manier doen:
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)
Dit is voor het Euler-algoritme! Ik vraag me af wat @fcard denkt over generieke symboolverzending! --- ^: engel:
Module Gotcha
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
Nauwkeuriger, werkt alleen binnen het topniveau van de module waarin de macro werd gedefinieerd.
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
dat dit gebeurt, maar standaardinstelling is altijd in strijd met het taalontwerp. Een goede verdediging hiervoor is om te voorkomen dat iemand namen in macro's gebruikt en introduceert, waardoor ze moeilijk te volgen zijn voor een menselijke lezer.
Python `dict` / JSON zoals syntaxis voor` Dict` -literalen.
Invoering
Julia gebruikt de volgende syntaxis voor woordenboeken:
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
Terwijl Python en JSON er zo uitzien:
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
Ter illustratie kunnen we deze syntaxis ook gebruiken in Julia en er nieuwe semantiek aan toevoegen ( Dict
syntaxis is de idiomatische manier in Julia, die wordt aanbevolen).
Laten we eerst kijken wat voor soort uitdrukking het is:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
Dit betekent dat we dit moeten nemen :cell1d
expressie en deze transformeren of een nieuwe expressie retourneren die er als volgt zou moeten uitzien:
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
Macro definitie
De volgende macro, hoewel eenvoudig, maakt het mogelijk dergelijke codegeneratie en -transformatie aan te tonen:
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
Laten we de resulterende macro-uitbreiding eens bekijken:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
Gebruik
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
Het laatste voorbeeld is exact gelijk aan:
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
misusage
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ₙ}`
Merk op dat Julia heeft ook andere toepassingen voor colon :
als zodanig je nodig hebt om het bereik letterlijke uitdrukkingen wrap met haakjes of gebruik de range
-functie, bijvoorbeeld.