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ühren

  • Lassen 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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow