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 adatti

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



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow