Julia Language
metaprogrammazione
Ricerca…
Sintassi
- nome macro (ex) ... fine
- citazione ... fine
- : (...)
- $ x
- Meta.quot (x)
- QuoteNode (x)
- esc (x)
Osservazioni
Le funzioni di metaprogrammazione di Julia sono fortemente ispirate a quelle delle lingue di tipo Lisp e sembreranno familiari a coloro che hanno un background Lisp. Metaprogramming è molto potente. Se usato correttamente, può portare a un codice più conciso e leggibile.
La quote ... end
è una sintassi quasi quote. Invece delle espressioni all'interno di essere valutate, vengono semplicemente analizzate. Il valore della quote ... end
espressione quote ... end
è l'Abstract Syntax Tree (AST) risultante.
La sintassi :(...)
è simile alla quote ... end
syntax, ma è più leggera. Questa sintassi è più concisa della quote ... end
.
All'interno di una quasiquote, l'operatore $
è speciale e interpola il suo argomento nell'AST. L'argomento dovrebbe essere un'espressione che è giuntata direttamente nell'AST.
La funzione Meta.quot(x)
cita il suo argomento. Questo è spesso utile in combinazione con l'uso di $
per l'interpolazione, in quanto consente letteralmente l'unione di espressioni e simboli nell'AST.
Reimplementare la macro @show
In Julia, la macro @show
è spesso utile per scopi di debug. Visualizza sia l'espressione da valutare che il suo risultato, restituendo infine il valore del risultato:
julia> @show 1 + 1
1 + 1 = 2
2
È semplice creare la nostra versione di @show
:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
Per usare la nuova versione, usa semplicemente la macro @myshow
:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
Fino al ciclo
Siamo tutti abituati alla sintassi while
, che esegue il suo corpo mentre la condizione viene valutata come true
. Cosa succede se vogliamo implementare un ciclo until
, che esegue un ciclo finché la condizione non viene valutata su true
?
In Julia, possiamo farlo creando una macro @until
, che si ferma ad eseguire il suo corpo quando la condizione è soddisfatta:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
Qui abbiamo usato la funzione chaining syntax |>
, che equivale a chiamare la funzione esc
sull'intero blocco di quote
. La funzione esc
impedisce all'igiene della macro di applicarsi al contenuto della macro; senza di essa, le variabili con scope nella macro verranno rinominate per evitare collisioni con variabili esterne. Vedi la documentazione di Julia sull'igiene macro per maggiori dettagli.
Puoi usare più di un'espressione in questo ciclo, semplicemente mettendo tutto in un begin ... end
blocco:
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 ed Expr (: quota)
Ci sono tre modi per citare qualcosa usando una funzione di Julia:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
Cosa significa "quoting" e a cosa serve? La citazione ci consente di proteggere le espressioni dall'interpretazione come forme speciali di Julia. Un caso di uso comune è quando generiamo espressioni che dovrebbero contenere elementi che valutano i simboli. (Ad esempio, questa macro deve restituire un'espressione che valuta un simbolo.) Non funziona semplicemente per restituire il simbolo:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
Cosa sta succedendo qui? @mysym
espande in :x
, che come espressione viene interpretata come variabile x
. Ma nulla è stato ancora assegnato a x
, quindi otteniamo un errore x not defined
.
Per aggirare questo, dobbiamo citare il risultato della nostra macro:
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
Qui, abbiamo usato la funzione Meta.quot
per trasformare il nostro simbolo in un simbolo quotato, che è il risultato che vogliamo.
Qual è la differenza tra Meta.quot
e QuoteNode
e quale dovrei usare? In quasi tutti i casi, la differenza non ha molta importanza. A volte è forse un po 'più sicuro usare QuoteNode
invece di Meta.quot
. Esplorare la differenza è informativo su come funzionano le espressioni e le macro di Julia.
La differenza tra Meta.quot
e QuoteNode
, spiegata
Ecco una regola generale:
- Se hai bisogno o vuoi supportare l'interpolazione, usa
Meta.quot
; - Se non puoi o non vuoi consentire l'interpolazione, usa
QuoteNode
.
In breve, la differenza è che Meta.quot
consente l'interpolazione all'interno della cosa quotata, mentre QuoteNode
protegge il suo argomento da qualsiasi interpolazione. Per capire l'interpolazione, è importante menzionare l'espressione $
. C'è una specie di espressione in Julia chiamata $
espressione. Queste espressioni consentono di scappare. Ad esempio, considera la seguente espressione:
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
Quando valutata, questa espressione valuterà 1
e assegnerà a x
, quindi costruirà un'espressione della forma _ + _
dove _
sarà sostituito dal valore di x
. Quindi, il risultato di questo dovrebbe essere l' espressione 1 + 1
(che non è ancora stata valutata, e quindi distinta dal valore 2
). In effetti, questo è il caso:
julia> eval(ex)
:(1 + 1)
Diciamo ora che stiamo scrivendo una macro per costruire questo tipo di espressioni. La nostra macro prenderà una discussione, che sostituirà la 1
nella ex
sopra. Questo argomento può essere qualsiasi espressione, ovviamente. Ecco qualcosa che non è esattamente ciò che vogliamo:
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
Il secondo caso non è corretto, perché dovremmo mantenere 1 + 1
valutato. Lo Meta.quot
citando l'argomento con 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
L'igiene macro non si applica al contenuto di una citazione, quindi in questo caso non è necessario scappare (e in effetti non è legale).
Come accennato in precedenza, Meta.quot
consente l'interpolazione. Quindi proviamolo:
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
Dal primo esempio, vediamo che l'interpolazione ci consente di allineare il sin(1)
, invece di fare in modo che l'espressione sia un sin(1)
letterale sin(1)
. Il secondo esempio mostra che questa interpolazione viene eseguita nell'ambito della macro invocazione, non nell'ambito della macro stessa. Questo perché la nostra macro non ha effettivamente valutato alcun codice; tutto ciò che sta facendo è generare codice. La valutazione del codice (che si fa strada nell'espressione) viene eseguita quando l'espressione generata dalla macro viene effettivamente eseguita.
E se invece avessimo usato QuoteNode
? Come puoi immaginare, dal momento che QuoteNode
impedisce che l'interpolazione si verifichi, ciò significa che non funzionerà.
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 questo esempio, potremmo essere d'accordo sul fatto che Meta.quot
offre una maggiore flessibilità, in quanto consente l'interpolazione. Quindi, perché potremmo mai considerare l'utilizzo di QuoteNode
? In alcuni casi, potremmo non desiderare realmente l'interpolazione e in realtà vogliamo l'espressione $
letterale. Quando sarebbe desiderabile? Consideriamo una generalizzazione di @makeex
cui possiamo passare ulteriori argomenti per determinare cosa viene a sinistra ea destra del segno +
:
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)
Una limitazione della nostra implementazione di @makeex4
è che non possiamo usare direttamente espressioni come i lati sinistro e destro dell'espressione, perché vengono interpolate. In altre parole, le espressioni possono essere valutate per l'interpolazione, ma potremmo volere che vengano mantenute. (Dato che ci sono molti livelli di citazione e valutazione qui, chiariamo: la nostra macro genera codice che costruisce un'espressione che, una volta valutata, produce un'altra espressione .)
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)
Dovremmo consentire all'utente di specificare quando deve avvenire l'interpolazione e quando non dovrebbe. In teoria, questa è una soluzione semplice: possiamo solo rimuovere uno dei $
segni nella nostra applicazione e lasciare che l'utente contribuisca al proprio. Ciò significa che interpoliamo una versione citata dell'espressione inserita dall'utente (che abbiamo già citato e interpolato una volta). Ciò porta al seguente codice, che può essere un po 'confuso all'inizio, a causa dei livelli multipli annidati di quoting e unquoting. Cerca di leggere e capire a cosa serve ciascuna fuga.
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
Le cose sono iniziate bene, ma qualcosa è andato storto. Il codice generato dalla macro sta tentando di interpolare la copia di y
nell'ambito della macro invocazione; ma non esiste una copia di y
nell'ambito della macro invocazione. Il nostro errore sta consentendo l'interpolazione con il secondo e il terzo argomento nella macro. Per correggere questo errore, dobbiamo usare 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)
Usando QuoteNode
, abbiamo protetto i nostri argomenti dall'interpolazione. Poiché QuoteNode
ha solo l'effetto di protezioni aggiuntive, non è mai dannoso usare QuoteNode
, a meno che non si desideri l'interpolazione. Tuttavia, capire la differenza rende possibile capire dove e perché Meta.quot
potrebbe essere una scelta migliore.
Questo lungo esercizio è con un esempio che è chiaramente troppo complesso per presentarsi in qualsiasi applicazione ragionevole. Pertanto, abbiamo giustificato la seguente regola empirica, menzionata in precedenza:
- Se hai bisogno o vuoi supportare l'interpolazione, usa
Meta.quot
; - Se non puoi o non vuoi consentire l'interpolazione, usa
QuoteNode
.
Che dire di Expr (: citazione)?
Expr(:quote, x)
è equivalente a Meta.quot(x)
. Tuttavia, quest'ultimo è più idiomatico ed è preferito. Per il codice che utilizza in gran parte la metaprogrammazione, viene spesso utilizzata una linea using Base.Meta
, che consente di Meta.quot
semplicemente come quot
.
Guida
Bit e bob di Metaprogramming di π
obiettivi:
Insegnare attraverso esempi funzionali minimamente mirati / utili / non astratti (es.
@swap
o@assert
) che introducono concetti in contesti adattiPreferisci che il codice illustri / mostri i concetti piuttosto che i paragrafi di spiegazione
Evita di collegare "lettura richiesta" ad altre pagine - interrompe la narrazione
Presentare le cose in un ordine ragionevole che renderà l'apprendimento più facile
risorse:
julialang.org
wikibook (@Cormullion)
5 strati (Leah Hanson)
SO-Doc Quoting (@TotalVerb)
SO-Doc - Simboli che non sono identificativi legali (@TotalVerb)
SO: Cos'è un simbolo in Julia (@StefanKarpinski)
Discussione discussione (@ pi- ) Metaprogrammazione
La maggior parte del materiale proviene dal canale del discorso, la maggior parte proviene dalla fcard ... perfavore se ho dimenticato le attribuzioni.
Simbolo
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
Passando flag in funzioni:
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
Un esempio di hashkey:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(Avanzato) (@fcard) :foo
aka :(foo)
restituisce un simbolo se foo
è un identificatore valido, altrimenti un'espressione.
# 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 ...
Fondamentalmente puoi trattare i simboli come stringhe leggere. Non è quello per cui sono, ma puoi farlo, quindi perché no. La base stessa di Julia lo fa, print_with_color(:red, "abc")
stampa un abc di colore rosso.
Expr (AST)
(Quasi) tutto in Julia è un'espressione, cioè un'istanza di Expr
, che manterrà un AST .
# when you type ... julia> 1+1 2 # Julia is doing: eval(parse("1+1")) # i.e. First it parses the string "1+1" into an `Expr` object ... julia> ast = parse("1+1") :(1 + 1) # ... which it then evaluates: julia> eval(ast) 2 # An Expr instance holds an AST (Abstract Syntax Tree). Let's look at it: julia> dump(ast) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Int64 1 typ: Any # TRY: fieldnames(typeof(ast)) julia> :(a + b*c + 1) == parse("a + b*c + 1") == Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1) true
Nesting Expr
s:
julia> dump( :(1+2/3) ) Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol / 2: Int64 2 3: Int64 3 typ: Any typ: Any # Tidier rep'n using s-expr julia> Meta.show_sexpr( :(1+2/3) ) (:call, :+, 1, (:call, :/, 2, 3))
Expr
multilinea usando la 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
... così il quote
è funzionalmente lo stesso ma fornisce informazioni di debug aggiuntive.
(*) SUGGERIMENTO : usare let
per mantenere x
all'interno del blocco
quote
una quote
Expr(:quote, x)
viene utilizzato per rappresentare le virgolette tra virgolette.
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
è simile a Expr(:quote, x)
ma impedisce l'interpolazione.
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( Disambigua i vari meccanismi di quotazione in metaprogrammazione di Julia
$ E : (...) sono in qualche modo inversi l'uno dall'altro?
:(foo)
significa "non guardare il valore, guarda l'espressione" $foo
significa "cambia l'espressione al suo valore"
:($(foo)) == foo
. $(:(foo))
è un errore. $(...)
non è un'operazione e non fa nulla da solo, è un "interpolare questo!" firmare che utilizza la sintassi del quoting. cioè Esiste solo all'interno di una citazione.
$
foo
lo stesso di eval(
foo
)
?
No! $foo
viene scambiato per il valore in fase di compilazione eval(foo)
significa eseguirlo in fase di runtime
eval
si verificherà nell'intervallo globale l'interpolazione è locale
eval(:<expr>)
dovrebbe restituire lo stesso di solo <expr>
(assumendo che <expr>
sia un'espressione valida nello spazio globale corrente)
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
s
Pronto? :)
# let's try to make this! julia> x = 5; @show x; x = 5
Facciamo la nostra macro @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
per abbassare un Expr
5 strati (Leah Hanson) <- spiega come Julia prende il codice sorgente come una stringa, lo converte in un Expr
-tree (AST), espande tutte le macro (ancora AST), abbassa (abbassato AST), quindi converte in LLVM (e oltre - al momento non abbiamo bisogno di preoccuparci di ciò che sta oltre!)
Q: code_lowered
agisce sulle funzioni. È possibile abbassare un Expr
? A: sì!
# 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)
Se vuoi espandere solo macro puoi usare 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
... che restituisce un AST non abbassato ma con tutte le macro espanse.
esc()
esc(x)
restituisce un Expr che dice "non applicare l'igiene a questo", è lo stesso di Expr(:escape, x)
. L'igiene è ciò che mantiene una macro autosufficiente, e si esc
cose se si vuole che "perdita". per esempio
Esempio: swap
macro per illustrare 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
$
ci consente di "scappare" dalla quote
. Quindi, perché non semplicemente $p
e $q
? vale a dire
# FAIL! tmp = $p $p = $q $q = tmp
Perché ciò dovrebbe guardare prima alla macro
scope per p
, e troverà un p
locale, cioè il parametro p
(sì, se si accede successivamente a p
senza esc
-ing, la macro considera il parametro p
come una variabile locale).
Quindi $p = ...
è solo un'assegnazione al p
locale. non influenza la variabile passata nel contesto di chiamata.
Ok, allora che ne dici:
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
Quindi esc(p)
sta "perdendo" p
nel contesto di chiamata. "La cosa che è stata passata nella macro che riceviamo come 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
Come puoi vedere, tmp
ottiene il trattamento igienico #10#tmp
, mentre x
e y
no. Julia sta creando un identificatore univoco per tmp
, qualcosa che puoi fare manualmente con gensym
, ovvero:
julia> gensym(:tmp) Symbol("##tmp#270")
Ma: c'è un trucco:
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
Un'altra cosa che fa l'igiene macro di Julia è che se la macro proviene da un altro modulo, rende qualsiasi variabile (che non è stata assegnata all'interno delle macro di ritorno dell'espressione della macro, come tmp
in questo caso) del modulo corrente, quindi $p
diventa Swap.$p
, allo stesso modo $q
-> Swap.$q
.
In generale, se hai bisogno di una variabile che si trova al di fuori dell'ambito della macro, dovresti esc, quindi dovresti esc(p)
ed esc(q)
indipendentemente dal fatto che siano sul LHS o RHS di un'espressione, o anche da soli.
la gente ha già parlato di gensym
poche volte e presto sarai sedotto dal lato oscuro del default a fuggire dall'intera espressione con qualche gensym
s punteggiato qua e là, ma ... Assicurati di capire come funziona l'igiene prima di provare ad essere più intelligente di quello! Non è un algoritmo particolarmente complesso, quindi non dovrebbe richiedere troppo tempo, ma non affrettarlo! Non usare quel potere finché non capisci tutte le implicazioni di esso ... (@fcard)
Esempio: 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) |>
è controverso, tuttavia. Sono sorpreso che una folla non sia ancora venuta a discutere. (forse tutti sono solo stanchi di ciò) Vi è una raccomandazione che la maggior parte se non tutta la macro sia solo una chiamata a una funzione, quindi:
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
... è un'alternativa più sicura
## @ Sfida macro semplice di fcard
Attività: swaps(1/2)
gli operandi, quindi gli swaps(1/2)
restituiscono 2.00
ossia 2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
Altre sfide macro da @fcard qui
Interpolazione e 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
Q: Perché l'ultimo $
? A: Interpola, cioè costringe Julia a eval
quella string(ex)
mentre l'esecuzione passa attraverso l'invocazione di questa macro. cioè se si esegue solo quel codice non forzerà alcuna valutazione. Ma nel momento in cui fai assert(foo)
Julia invocherà questa macro sostituendo il suo 'token AST / Expr' con qualunque cosa ritorni, e il $
entrerà in azione.
Un divertente trucco per usare {} per i blocchi
(@fcard) Non penso che ci sia qualcosa di tecnico {}
da utilizzare come blocchi, in effetti si può anche fare un gioco di parole sulla sintassi residua {}
per farlo funzionare:
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
* (è improbabile che funzioni ancora se / quando la sintassi di {} viene riproposta)
Quindi, per prima cosa, Julia vede il token macro, quindi leggerà / analizzerà i token fino alla end
corrispondente e creerà cosa? Un Expr
con .head=:macro
o qualcosa del genere? Memorizza "a+1"
come stringa o lo suddivide in :+(:a, 1)
? Come visualizzare?
?
(@fcard) In questo caso a causa dell'ambito lessicale, a non è definito @M
s, quindi usa la variabile globale ... In realtà ho dimenticato di sfuggire all'espressione di flipplin nel mio esempio stupido, ma "funziona solo all'interno del stesso modulo " parte di esso si applica ancora.
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
Il motivo è che, se la macro viene utilizzata in qualsiasi modulo diverso da quello in cui è stata definita, tutte le variabili non definite all'interno del codice da espandere sono considerate globali del modulo della macro.
julia> macroexpand(:(M.@m)) :(M.a + 1)
AVANZATE
### @ 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
La macro di Scott:
"""
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 / unprocessed ...
visualizza / scarica una macro
(@ pi-) Supponiamo di fare solo macro m(); a+1; end
con un nuovo REPL. Senza a
definito. Come posso 'vederlo'? come, c'è un modo per "scaricare" una macro? Senza effettivamente eseguirlo
(@fcard) Tutti i codici nelle macro sono in realtà messi in funzioni, quindi è possibile visualizzare solo il codice abbassato o il tipo derivato.
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!
Altri modi per ottenere la funzione di una macro:
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)
^ sembra che io possa usare invece code_typed
Come capire eval(Symbol("@M"))
?
(@fcard) Attualmente, ogni macro ha una funzione ad essa associata. Se hai una macro chiamata M
, la funzione della macro si chiama @M
. Generalmente è possibile ottenere il valore di una funzione con ad esempio eval(:print)
ma con la funzione macro è necessario fare Symbol("@M")
, poiché proprio :@M
diventa un Expr(:macrocall, Symbol("@M"))
e la valutazione che causa una macro-espansione.
Perché non code_typed
visualizzati i parametri di visualizzazione code_typed
?
(@pi-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ qui vedo uno ::Any
parametro, ma non sembra essere collegato con il token x
.
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^ allo stesso modo qui; non c'è niente da collegare io
con il ::IO
Quindi sicuramente questo non può essere un dump completo della rappresentazione AST di quel particolare metodo di print
...?
(@fcard) print(::IO, ::Char)
ti dice solo quale metodo è, non fa parte dell'AST. Non è più presente nemmeno nel master:
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-) Non capisco cosa intendi con questo. Sembra che si stia scaricando l'AST per il corpo di quel metodo, no? Pensavo che code_typed
una funzione. Ma sembra che manchi il primo passo, cioè impostare i token per i parametri.
(@fcard) code_typed
scopo di mostrare solo l'AST del corpo, ma per ora fornisce l'AST completo del metodo, sotto forma di LambdaInfo
(0.5) o CodeInfo
(0.6), ma molte informazioni vengono omesse quando viene stampato sul repl. Dovrai ispezionare il campo LambdaInfo
per campo per ottenere tutti i dettagli. dump
invaderà il tuo repl, quindi potresti provare:
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])
Che fornisce tutti i valori dei campi nominati dell'AST di un metodo:
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)
Vedi lil ' def = print(io::IO, c::Char)
? Ecco qua! (anche lo slotnames = [..., :io, :c]
parte) Inoltre sì, la differenza di output è perché stavo mostrando i risultati su master.
???
(@ Ismael-VC) intendi in questo modo? Spedizione generica con simboli
Puoi farlo in questo modo:
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)
Questo è per l'algoritmo di Eulero! Mi chiedo cosa pensi @fcard della spedizione di simboli generici! --- ^: angelo:
Modulo Gotcha
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
Più precisamente, funziona solo all'interno del livello superiore del modulo in cui è stata definita la macro.
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
evita che ciò accada, ma il default di usarlo sempre va contro il design del linguaggio. Una buona difesa per questo è impedirne l'uso e l'introduzione di nomi all'interno di macro, il che rende difficile rintracciare un lettore umano.
Python `dict` / JSON come sintassi per i letterali` Dict`.
introduzione
Julia usa la seguente sintassi per i dizionari:
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
Mentre Python e JSON hanno questo aspetto:
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
A scopo illustrativo potremmo anche usare questa sintassi in Julia e aggiungere nuove semantiche (la sintassi di Dict
è il modo idiomatico in Julia, che è raccomandato).
Per prima cosa vediamo che tipo di espressione è:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
Ciò significa che dobbiamo prendere questa :cell1d
espressione :cell1d
e trasformarla o restituire una nuova espressione che dovrebbe assomigliare a questa:
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
Definizione macro
La seguente macro, sebbene semplice, consente di dimostrare tale generazione e trasformazione del codice:
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
Diamo un'occhiata all'espansione della macro risultante:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
uso
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
L'ultimo esempio è esattamente equivalente a:
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
cattivo uso
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ₙ}`
Si noti che Julia ha altri usi per i due punti :
in questo modo è necessario includere le espressioni letterali con parentesi o utilizzare la funzione range
, ad esempio.