Julia Language
Metaprogrammierung
Suche…
Syntax
- Makroname (ex) ... Ende
- Zitat ... Ende
- : (...)
- $ x
- Meta.quot (x)
- QuoteNode (x)
- esc (x)
Bemerkungen
Julias Metaprogrammierungsfunktionen sind stark von denen von Lisp-ähnlichen Sprachen inspiriert und werden denen mit Lisp-Hintergrund bekannt vorkommen. Metaprogrammierung ist sehr mächtig. Bei korrekter Verwendung kann es zu prägnanterem und lesbarerem Code führen.
Das quote ... end
ist quasiquote Syntax. Anstelle der Ausdrücke, die ausgewertet werden, werden sie einfach analysiert. Der Wert des quote ... end
ist der resultierende Abstract Syntax Tree (AST).
Die Syntax :(...)
ähnelt der Syntax quote ... end
, ist jedoch leichter. Diese Syntax ist knapper als quote ... end
.
In einer Quasiquote ist der Operator $
Besonderes und interpoliert sein Argument in die AST. Es wird erwartet, dass das Argument ein Ausdruck ist, der direkt mit dem AST verbunden ist.
Die Meta.quot(x)
-Funktion zitiert ihr Argument. Dies ist häufig in Kombination mit $
für die Interpolation hilfreich, da Ausdrücke und Symbole buchstäblich mit dem AST verbunden werden können.
Das @show-Makro erneut implementieren
In Julia ist das @show
Makro häufig für Debugging-Zwecke hilfreich. Es zeigt sowohl den auszuwertenden Ausdruck als auch das Ergebnis an und gibt schließlich den Wert des Ergebnisses zurück:
julia> @show 1 + 1
1 + 1 = 2
2
Es ist einfach, eine eigene Version von @show
zu erstellen:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
Um die neue Version zu verwenden, verwenden Sie einfach das @myshow
Makro:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
Bis zur Schleife
Wir sind alle an die while
Syntax gewöhnt, die ihren Körper ausführt, während die Bedingung als true
bewertet wird. Was passiert , wenn wir wollen , eine implementieren , until
Schleife führt , dass eine Schleife , bis die Bedingung ausgewertet wird true
?
In Julia können Sie dies tun, indem Sie ein @until
Makro @until
, das seinen Körper stoppt, wenn die Bedingung erfüllt ist:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
Hier haben wir die Funktionsverkettungssyntax |>
, die dem Aufruf der esc
Funktion für den gesamten quote
. Die esc
Funktion verhindert, dass Makrohygiene auf den Inhalt des Makros angewendet wird. Andernfalls werden Variablen im Makro umbenannt, um Kollisionen mit externen Variablen zu vermeiden. Weitere Informationen finden Sie in der Julia-Dokumentation zur Makrohygiene .
Sie können mehrere Ausdrücke in dieser Schleife verwenden, indem Sie einfach alles in einen begin ... end
einfügen:
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 und Expr (: Quote)
Es gibt drei Möglichkeiten, etwas mit einer Julia-Funktion zu zitieren:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
Was bedeutet "Zitieren" und wofür ist es gut? Durch Zitieren können wir Ausdrücke davor schützen, von Julia als Sonderformen interpretiert zu werden. Ein häufiger Anwendungsfall ist das Generieren von Ausdrücken , die Elemente enthalten sollten, die Symbole auswerten. ( Dieses Makro muss beispielsweise einen Ausdruck zurückgeben, der ein Symbol auswertet.) Es funktioniert nicht einfach, um das Symbol zurückzugeben:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
Was ist denn hier los? @mysym
erweitert sich zu :x
, das als Ausdruck als Variable x
interpretiert wird. x
noch nichts zugewiesen, so dass wir einen x not defined
Fehler erhalten.
Um dies zu umgehen, müssen wir das Ergebnis unseres Makros zitieren:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Hier haben wir die Meta.quot
Funktion verwendet, um unser Symbol in ein in Anführungszeichen gesetztes Symbol zu verwandeln. Dies ist das gewünschte Ergebnis.
Was ist der Unterschied zwischen Meta.quot
und QuoteNode
und welchen sollte ich verwenden? In fast allen Fällen spielt der Unterschied keine Rolle. Es ist vielleicht etwas sicherer, QuoteNode
anstelle von Meta.quot
. Die Untersuchung des Unterschieds ist jedoch aufschlussreich, wie Julia-Ausdrücke und -Makros funktionieren.
Der Unterschied zwischen Meta.quot
und QuoteNode
wird erläutert
Hier ist eine Faustregel:
- Wenn Sie die Interpolation benötigen oder unterstützen möchten, verwenden Sie
Meta.quot
. - Wenn Sie die Interpolation nicht zulassen können oder wollen, verwenden Sie
QuoteNode
.
Kurz gesagt, der Unterschied besteht darin, dass Meta.quot
die Interpolation innerhalb der zitierten Sache erlaubt, während QuoteNode
sein Argument vor jeder Interpolation schützt. Um die Interpolation zu verstehen, muss der Ausdruck $
. Es gibt eine Art Ausdruck in Julia, den Ausdruck $
. Diese Ausdrücke ermöglichen die Flucht. Betrachten Sie zum Beispiel den folgenden Ausdruck:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Bei der Auswertung wertet dieser Ausdruck 1
ordnet es x
und erstellt dann einen Ausdruck der Form _ + _
wobei _
durch den Wert von x
. Daher sollte das Ergebnis der Ausdruck 1 + 1
(der noch nicht ausgewertet wurde und sich daher vom Wert 2
). In der Tat ist dies der Fall:
julia> eval(ex)
:(1 + 1)
Nehmen wir an, wir schreiben ein Makro, um diese Art von Ausdrücken zu erstellen. Unser Makro wird ein Argument annehmen, das die 1
im obigen ex
ersetzen wird. Dieses Argument kann natürlich jeder Ausdruck sein. Hier ist etwas, was wir nicht wollen:
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
Der zweite Fall ist falsch, weil wir 1 + 1
ausgewertet lassen sollten. Wir beheben das, indem wir das Argument mit 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
Die Makrohygiene gilt nicht für den Inhalt eines Angebots. In diesem Fall ist eine Flucht nicht erforderlich (und in der Tat nicht legal).
Wie bereits erwähnt, ermöglicht Meta.quot
die Interpolation. Also lassen Sie uns das ausprobieren:
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
Im ersten Beispiel sehen wir, dass die Interpolation es uns ermöglicht, die sin(1)
zu inline zu setzen, anstatt dass der Ausdruck eine wörtliche sin(1)
. Das zweite Beispiel zeigt, dass diese Interpolation im Bereich des Makroaufrufs erfolgt, nicht im eigenen Bereich des Makros. Das liegt daran, dass unser Makro keinen Code ausgewertet hat. Alles, was es tut, ist Code zu generieren. Die Auswertung des Codes (der in den Ausdruck gelangt) wird ausgeführt, wenn der vom Makro generierte Ausdruck tatsächlich ausgeführt wird.
Was wäre, wenn wir stattdessen QuoteNode
verwendet QuoteNode
? Wie Sie sich QuoteNode
können, da QuoteNode
die Interpolation überhaupt nicht QuoteNode
, bedeutet dies, dass es nicht funktioniert.
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 diesem Beispiel könnten wir zustimmen, dass Meta.quot
mehr Flexibilität bietet, da es die Interpolation ermöglicht. Warum können wir QuoteNode
überhaupt in Betracht QuoteNode
? In einigen Fällen wünschen wir uns eigentlich keine Interpolation und den buchstäblichen $
-Ausdruck. Wann wäre das wünschenswert? Lassen Sie sich eine Verallgemeinerung betrachten @makeex
wo wir zusätzliche Argumente bestimmen , was kommt nach links und rechts neben dem passieren können +
Zeichen:
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)
Eine Einschränkung unserer Implementierung von @makeex4
besteht darin, dass wir Ausdrücke nicht direkt als linke oder rechte Seite des Ausdrucks verwenden können, da sie interpoliert werden. Mit anderen Worten, die Ausdrücke werden möglicherweise für die Interpolation ausgewertet, wir möchten jedoch, dass sie beibehalten werden. (Da es hier viele Ebenen des Zitierens und der Bewertung gibt, lassen Sie uns klarstellen: Unser Makro generiert Code , der einen Ausdruck erstellt , der bei Auswertung einen anderen Ausdruck erzeugt. Puh!)
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)
Wir sollten dem Benutzer erlauben zu bestimmen, wann Interpolation stattfinden soll und wann nicht. Theoretisch ist das eine einfache Lösung: Wir können einfach eines der $
-Zeichen in unserer Anwendung entfernen und den Benutzer selbst einbringen. Dies bedeutet, dass wir eine zitierte Version des vom Benutzer eingegebenen Ausdrucks interpolieren (die wir bereits einmal zitiert und interpoliert haben). Dies führt zu dem folgenden Code, der aufgrund der verschachtelten Ebenen des Zitierens und des Nicht-Zitierens zunächst etwas verwirrend sein kann. Versuche zu lesen und zu verstehen, wozu jede Flucht dient.
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
Die Dinge haben gut angefangen, aber etwas ist schief gelaufen. Der generierte Code des Makros versucht, die Kopie von y
im Makroaufrufbereich zu interpolieren. Im Makroaufrufbereich befindet sich jedoch keine Kopie von y
. Unser Fehler ist die Interpolation mit den zweiten und dritten Argumenten im Makro. Um diesen Fehler zu beheben, müssen wir 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)
Durch die Verwendung von QuoteNode
haben wir unsere Argumente vor Interpolation geschützt. Da QuoteNode
nur als zusätzlicher Schutz wirkt, ist die Verwendung von QuoteNode
niemals schädlich, es sei denn, Sie QuoteNode
Interpolation. Meta.quot
Sie den Unterschied verstehen, können Sie jedoch verstehen, wo und warum Meta.quot
die bessere Wahl sein könnte.
Diese lange Übung ist an einem Beispiel, das offensichtlich zu komplex ist, um in einer vernünftigen Anwendung gezeigt zu werden. Daher haben wir die bereits erwähnte folgende Faustregel gerechtfertigt:
- Wenn Sie die Interpolation benötigen oder unterstützen möchten, verwenden Sie
Meta.quot
. - Wenn Sie die Interpolation nicht zulassen können oder wollen, verwenden Sie
QuoteNode
.
Was ist mit Expr (: Zitat)?
Expr(:quote, x)
entspricht Meta.quot(x)
. Letzteres ist jedoch eher idiomatisch und wird bevorzugt. Für Code, den metaprogramming, eine stark nutzt using Base.Meta
Linie wird oft verwendet, die ermöglicht Meta.quot
wird bezeichnet als einfach quot
.
Führen
π's Metaprogrammier-Bits und -Bobs
Tore:
Lehren Sie durch minimale gezielte funktionale / nützliche / nicht abstrakte Beispiele (z. B.
@swap
oder@assert
), die Konzepte in geeigneten Kontexten einführenLassen Sie den Code lieber die Konzepte als Erklärungsabschnitte veranschaulichen / veranschaulichen
Vermeiden Sie das Verlinken des "erforderlichen Lesens" auf andere Seiten, da dies die Erzählung unterbricht
Präsentieren Sie die Dinge in einer sinnvollen Reihenfolge, die das Lernen am einfachsten macht
Ressourcen:
julialang.org
wikibook (@Cormullion)
5 Schichten (Leah Hanson)
SO-Doc-Notierung (@TotalVerb)
SO-Doc - Symbole, die keine zulässigen Bezeichner sind (@TotalVerb)
SO: Was ist ein Symbol in Julia (@StefanKarpinski)
Diskursthread (@ pi-) Metaprogrammierung
Das meiste Material stammt aus dem Diskurskanal, das meiste stammt von fcard ... bitte stoße mich, wenn ich Attributionen vergessen hätte.
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
Flaggen an Funktionen übergeben:
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
Ein Hashschlüssel-Beispiel:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(Fortgeschritten) (@fcard) :foo
aka :(foo)
ergibt ein Symbol, wenn foo
ein gültiger Bezeichner ist, andernfalls ein Ausdruck.
# 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 ...
Grundsätzlich können Sie Symbole als leichte Zeichenfolgen behandeln. Dafür gibt es sie nicht, aber Sie können es tun, also warum nicht. Julias Base selbst macht es, print_with_color(:red, "abc")
druckt ein rotes abc.
Ausdruck (AST)
(Fast) alles in Julia ist ein Ausdruck, dh eine Instanz von Expr
, die einen AST enthalten wird .
# 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))
mehrzeilige Expr
mit 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
... also quote
ist funktionell das gleiche, bietet aber zusätzliche Debug-Informationen.
(*) TIPP : Verwenden Sie let
, um x
innerhalb des Blocks zu halten
quote
-ing ein quote
Expr(:quote, x)
werden verwendet, um Anführungszeichen innerhalb von Anführungszeichen darzustellen.
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
ähnelt Expr(:quote, x)
, verhindert jedoch Interpolation.
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( Erläutern Sie die verschiedenen Zitiermechanismen in der Julia-Metaprogrammierung
Sind $ und : (…) sich irgendwie gegenseitig umkehren?
:(foo)
bedeutet "nicht auf den Wert schauen, sondern auf den Ausdruck" $foo
bedeutet "den Ausdruck in seinen Wert ändern"
:($(foo)) == foo
. $(:(foo))
ist ein Fehler. $(...)
ist keine Operation und macht nichts von sich aus, es ist ein "interpoliere das!" kennzeichnen, dass die Anführungszeichen-Syntax verwendet. dh es existiert nur innerhalb eines Zitats.
Ist $
foo
dasselbe wie eval(
foo
)
?
Nein! $foo
wird gegen die Compile-Time- eval(foo)
- eval(foo)
ausgetauscht, um dies zur Laufzeit zu tun
eval
wird im globalen Gültigkeitsbereich auftreten. Interpolation ist lokal
eval(:<expr>)
sollte das gleiche wie nur <expr>
(vorausgesetzt, <expr>
ist ein gültiger Ausdruck im aktuellen globalen Bereich).
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
s
Bereit? :)
# let's try to make this! julia> x = 5; @show x; x = 5
Lassen Sie uns unser eigenes @show
Makro @show
:
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
, um einen Expr
5 Schichten (Leah Hanson) <- erklärt, wie Julia Quellcode als Zeichenfolge verwendet, in einen Expr
Baum (AST) einkennzeichnet, alle Makros (immer noch AST) ausdehnt, senkt ( verringert AST) und konvertiert dann in LLVM (und darüber hinaus - im Moment brauchen wir uns keine Sorgen zu machen, was dahinter liegt!)
F: code_lowered
wirkt auf Funktionen. Ist es möglich, einen Expr
zu senken? 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)
Wenn Sie nur Makros erweitern möchten, können Sie 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
... das eine nicht abgesenkte AST zurückgibt, aber alle Makros erweitert sind.
esc()
esc(x)
gibt einen Ausdruck zurück, der besagt "Keine Hygiene anwenden", es ist dasselbe wie Expr(:escape, x)
. Hygiene ist das, was ein Makro in sich hält, und Sie müssen Dinge esc
wenn Sie möchten, dass sie "auslaufen". z.B
Beispiel: swap
Makro zur Veranschaulichung von 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
$
können wir dem quote
entkommen. Warum also nicht einfach $p
und $q
? dh
# FAIL! tmp = $p $p = $q $q = tmp
Da dies zunächst nach dem macro
Gültigkeitsbereich für p
suchen würde, würde es ein lokales p
dh den Parameter p
(ja, wenn Sie anschließend ohne esc
-ing auf p
zugreifen, betrachtet das Makro den p
Parameter als lokale Variable).
$p = ...
ist also nur eine Zuordnung zum lokalen p
. Es wirkt sich nicht auf die Variable aus, die im aufrufenden Kontext übergeben wurde.
Ok, wie wäre es mit:
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
So esc(p)
ist 'undichte' p
in den anrufenden Kontext. "Die Sache, die in das Makro übergeben wurde, das wir 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
Wie Sie sehen, erhält tmp
die Hygienebehandlung #10#tmp
, während x
und y
dies nicht tun. Julia macht einen eindeutigen Bezeichner für tmp
, den Sie manuell mit gensym
tun gensym
, z. gensym
.:
julia> gensym(:tmp) Symbol("##tmp#270")
Aber: Es gibt ein Problem:
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
Eine weitere Sache, die Julias Makrohygiene bewirkt, ist, dass das Makro, wenn es aus einem anderen Modul stammt, alle Variablen (die nicht im zurückgegebenen Ausdruck des Makros, wie in diesem Fall tmp
zugewiesen wurden) zu globalen Werten des aktuellen Moduls macht, sodass $p
zu Swap.$p
, ebenfalls $q
-> Swap.$q
.
Wenn Sie eine Variable außerhalb des Gültigkeitsbereichs des Makros benötigen, sollten Sie esc esc machen. esc(p)
sollten Sie esc(p)
und esc(q)
unabhängig davon, ob sie sich auf der LHS oder der RHS eines Ausdrucks befinden oder sogar für sich alleine.
Die Leute haben gensym
einige Male erwähnt, und schon bald werden Sie von der dunklen Seite des gensym
verführt, dem ganzen Ausdruck mit ein paar gensym
s hier und da zu entgehen, aber ... Machen Sie sich mit Hygiene gensym
, bevor Sie versuchen zu sein schlauer als es! Es ist kein besonders komplexer Algorithmus, also sollte es nicht zu lange dauern, aber übertreiben Sie es nicht! Nutze diese Macht erst, wenn du alle Auswirkungen davon verstanden hast ... (@fcard)
Beispiel: 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) |>
ist jedoch umstritten. Ich bin überrascht, dass sich ein Mob noch nicht zur Diskussion gestellt hat. (Vielleicht ist jeder einfach müde). Es wird empfohlen, dass die meisten, wenn nicht alle, nur einen Aufruf einer Funktion haben, also:
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
... ist eine sicherere Alternative.
## @ fcard's einfache Makro-Herausforderung
Aufgabe: Tauschen Sie die Operanden aus, also ergibt swaps(1/2)
2.00
dh 2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
Weitere Makro-Herausforderungen von @fcard hier
Interpolations- und assert
Makro
http://docs.julialang.org/de/release-0.5/manual/metaprogramming/#building-an-advanced-macro
macro assert(ex) return :( $ex ? nothing : throw(AssertionError($(string(ex)))) ) end
Q: warum der letzte $
? A: Es interpoliert, dh zwingt Julia, diese string(ex)
eval
, wenn die Ausführung den Aufruf dieses Makros durchläuft. Wenn Sie diesen Code nur ausführen, wird keine Bewertung erzwungen. Aber in dem Moment, in dem Sie assert(foo)
Julia dieses Makro aufrufen und seinen 'AST-Token / Ausdruck' durch das ersetzen, was es zurückgibt, und $
wird in Aktion treten.
Ein lustiger Hack für die Verwendung von {} für Blöcke
(@fcard) Ich glaube nicht, dass es technische Gründe gibt, {}
die als Blöcke verwendet werden. Tatsächlich kann man sogar die restliche {}
-Syntax unterdrücken, damit es funktioniert:
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
* (funktioniert wahrscheinlich nicht, wenn / wenn die {} -Syntax neu verwendet wird)
Also sieht Julia zuerst das Makro-Token, liest / parst Token bis zum passenden end
und erstellt was? Ein Expr
mit .head=:macro
oder etwas? Speichert es "a+1"
als Zeichenfolge oder zerlegt es in :+(:a, 1)
? Wie sehen Sie?
?
(@fcard) In diesem Fall ist a wegen des lexikalischen Gültigkeitsbereichs im @M
undefiniert, daher wird die globale Variable verwendet. Ich habe tatsächlich vergessen, den flipplin'-Ausdruck in meinem blöden Beispiel zu ignorieren, aber das "funktioniert nur innerhalb von Dasselbe Modul " Teil davon gilt noch.
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
Der Grund ist, dass, wenn das Makro in einem anderen Modul als dem verwendet wird, in dem es definiert wurde, alle Variablen, die nicht im zu erweiternden Code definiert sind, als Globals des Moduls des Makros behandelt werden.
julia> macroexpand(:(M.@m)) :(M.a + 1)
ERWEITERT
### @ 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 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
Junk / unverarbeitet ...
ein Makro anzeigen / ausgeben
(@ pi-) Angenommen, ich mache einfach macro m(); a+1; end
in einer frischen REPL. Ohne a
definiertes. Wie kann ich es "sehen"? Gibt es eine Möglichkeit, ein Makro zu "entleeren"? Ohne es tatsächlich auszuführen
(@fcard) Der gesamte Code in Makros wird tatsächlich in Funktionen eingefügt, sodass Sie nur den herabgesetzten oder vom Typ abgeleiteten Code anzeigen können.
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 Möglichkeiten, eine Makrofunktion zu erhalten:
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)
^ code_typed
kann code_typed
stattdessen code_typed
verwenden
Wie kann man eval(Symbol("@M"))
verstehen?
(@fcard) Derzeit ist jedem Makro eine Funktion zugeordnet. Wenn Sie ein Makro mit dem Namen M
, heißt die Funktion des Makros @M
. Im Allgemeinen können Sie einen Funktionswert mit zB eval(:print)
aber mit einer Makrofunktion müssen Sie Symbol("@M")
:@M
, da aus :@M
ein Expr(:macrocall, Symbol("@M"))
und Auswertung, die eine Makro-Erweiterung bewirkt.
Warum zeigt code_typed
keine code_typed
an?
(@Pi-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ hier sehe ich einen ::Any
Parameter, aber er scheint nicht mit dem Token x
.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^ hier ähnlich; Es gibt nichts, was io
mit dem ::IO
verbinden kann. Dies kann jedoch kein vollständiger Speicherauszug der AST-Darstellung dieser speziellen print
…?
(@fcard) print(::IO, ::Char)
sagt Ihnen nur, um welche Methode es sich handelt, sie ist nicht Teil des AST. Es ist nicht einmal mehr in master vorhanden:
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) Ich verstehe nicht, was du damit meinst. Es scheint, die AST für den Körper dieser Methode zu entleeren, nein? Ich dachte, code_typed
gibt den AST für eine Funktion an. Der erste Schritt scheint jedoch zu fehlen, dh das Einrichten von Token für Params.
(@fcard) code_typed
soll nur die AST des Körpers anzeigen, aber code_typed
gibt es den vollständigen AST der Methode in Form einer LambdaInfo
(0.5) oder CodeInfo
(0.6) an, aber viele Informationen werden weggelassen wenn auf der Replik gedruckt. Sie müssen das LambdaInfo
Feld nach Feld untersuchen, um alle Details zu erhalten. dump
wird Ihre Antwort überfluten, so dass Sie versuchen könnten:
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])
Gibt alle Werte der benannten Felder der AST einer Methode an:
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)
Sehen Sie das lil ' def = print(io::IO, c::Char)
? Da gehts! (auch die slotnames = [..., :io, :c]
part) Auch ja, der Unterschied in der Ausgabe liegt darin, dass ich die Ergebnisse auf Master slotnames = [..., :io, :c]
.
???
(@ Ismael-VC) meinst du so? Generischer Versand mit Symbolen
Sie können es so machen:
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)
Dies ist für den Euler-Algorithmus! Ich frage mich, was @fcard über den generischen Symbolversand denkt! --- ^: Engel:
Modul Gotcha
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
Genauer, funktioniert nur innerhalb der obersten Ebene des Moduls, in dem das Makro definiert wurde.
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
dies geschieht, aber wenn es standardmäßig verwendet wird, widerspricht es dem Sprachdesign. Eine gute Verteidigung dafür ist, dass man keine Namen in Makros verwenden und einführen kann, was es schwierig macht, sie einem menschlichen Leser zu finden.
Python `dict` / JSON-ähnliche Syntax für` Dict`-Literale.
Einführung
Julia verwendet die folgende Syntax für Wörterbücher:
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
Während Python und JSON so aussehen:
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
Zu Illustrationszwecken könnten wir diese Syntax auch in Julia verwenden und neue Semantik hinzufügen ( Dict
Syntax ist die idiomatische Art in Julia, die empfohlen wird).
Lassen Sie uns zunächst sehen , welche Art von Ausdruck ist:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
Das bedeutet, dass wir :cell1d
nehmen müssen :cell1d
Ausdruck und entweder transformieren oder einen neuen Ausdruck zurückgeben, der folgendermaßen aussehen sollte:
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
Makrodefinition
Mit dem folgenden Makro können Sie zwar eine solche Codegenerierung und -transformation demonstrieren:
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
Schauen wir uns die resultierende Makro-Erweiterung an:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
Verwendungszweck
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
Das letzte Beispiel ist genau gleichbedeutend mit:
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
Missbrauch
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ₙ}`
Beachten Sie, dass Julia andere Verwendungsmöglichkeiten für Doppelpunkte hat :
Als solche müssen Sie Bereichsliteralausdrücke mit Klammern umschließen oder beispielsweise die range
verwenden.