Zoeken…


Syntaxis

  • macro naam (ex) ... einde
  • citaat ... einde
  • : (...)
  • $ x
  • Meta.quot (x)
  • QuoteNode (x)
  • esc (x)

Opmerkingen

Julia's metaprogrammeringsfuncties zijn sterk geïnspireerd door die van Lisp-achtige talen en zullen bekend zijn voor mensen met een Lisp-achtergrond. Metaprogrammering is erg krachtig. Bij correct gebruik kan dit leiden tot meer beknopte en leesbare code.

Het quote ... end is de syntaxis van quasiquote. In plaats van de uitdrukkingen die worden geëvalueerd, worden ze eenvoudigweg ontleed. De waarde van de quote ... end expressie is de resulterende Abstract Syntax Tree (AST).

De :(...) syntaxis lijkt op de quote ... end syntaxis, maar is lichter. Deze syntaxis is beknopter dan quote ... end .

Binnen een quasiquote, de $ operator is speciaal en interpoleert haar betoog in de AST. Verwacht wordt dat het argument een uitdrukking is die direct in de AST wordt gesplitst.

De functie Meta.quot(x) citeert zijn argument. Dit is vaak handig in combinatie met het gebruik van $ voor interpolatie, omdat uitdrukkingen en symbolen letterlijk in de AST kunnen worden gesplitst.

De @show-macro opnieuw implementeren

In Julia is de macro @show vaak handig voor het opsporen van fouten. Het toont zowel de te evalueren uitdrukking als het resultaat ervan, en retourneert uiteindelijk de waarde van het resultaat:

julia> @show 1 + 1
1 + 1 = 2
2

Het is eenvoudig om onze eigen versie van @show :

julia> macro myshow(expression)
           quote
               value = $expression
               println($(Meta.quot(expression)), " = ", value)
               value
           end
       end

Om de nieuwe versie te gebruiken, gebruikt u eenvoudig de macro @myshow :

julia> x = @myshow 1 + 1
1 + 1 = 2
2

julia> x
2

Tot lus

We zijn allemaal gewend aan de syntaxis van while , die zijn lichaam uitvoert terwijl de voorwaarde als true wordt geëvalueerd. Wat als we een until lus willen implementeren, die een lus uitvoert totdat de voorwaarde als true is geëvalueerd?

In Julia kunnen we dit doen door een @until macro te maken, die stopt met het uitvoeren van zijn lichaam wanneer aan de voorwaarde is voldaan:

macro until(condition, expression)
    quote
        while !($condition)
            $expression
        end
    end |> esc
end

Hier hebben we de functie chaining syntax |> , wat gelijk staat aan het aanroepen van de esc functie op het hele quote blok. De esc functie voorkomt dat esc wordt toegepast op de inhoud van de macro; zonder dit zullen variabelen die in de macro zijn opgenomen een nieuwe naam krijgen om botsingen met externe variabelen te voorkomen. Raadpleeg de Julia-documentatie over macrohygiëne voor meer informatie.

Je kunt meer dan één uitdrukking in deze lus gebruiken, door eenvoudig alles in een begin ... end eindblok te plaatsen:

julia> i = 0;

julia> @until i == 10 begin
           println(i)
           i += 1
       end
0
1
2
3
4
5
6
7
8
9

julia> i
10

QuoteNode, Meta.quot en Expr (: quote)

Er zijn drie manieren om iets te citeren met behulp van een Julia-functie:

julia> QuoteNode(:x)
:(:x)

julia> Meta.quot(:x)
:(:x)

julia> Expr(:quote, :x)
:(:x)

Wat betekent "citeren" en waar is het goed voor? Met citeren kunnen we voorkomen dat uitdrukkingen door Julia als speciale vormen worden geïnterpreteerd. Een veelvoorkomend geval is wanneer we uitdrukkingen genereren die dingen moeten bevatten die evalueren naar symbolen. ( Deze macro moet bijvoorbeeld een uitdrukking retourneren die resulteert in een symbool.) Het werkt niet eenvoudig om het symbool terug te geven:

julia> macro mysym(); :x; end
@mysym (macro with 1 method)

julia> @mysym
ERROR: UndefVarError: x not defined

julia> macroexpand(:(@mysym))
:x

Wat is hier aan de hand? @mysym breidt uit naar :x , dat als een uitdrukking wordt geïnterpreteerd als de variabele x . Maar er is nog niets aan x toegewezen, dus we krijgen een x not defined foutmelding.

Om dit te omzeilen, moeten we het resultaat van onze macro citeren:

julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)

julia> @mysym2
:x

julia> macroexpand(:(@mysym2))
:(:x)

Hier hebben we de functie Meta.quot gebruikt om van ons symbool een aanhalingsteken te maken, wat we willen.

Wat is het verschil tussen Meta.quot en QuoteNode en welke moet ik gebruiken? In bijna alle gevallen doet het verschil er niet echt toe. Het is misschien een beetje veiliger om QuoteNode te gebruiken QuoteNode plaats van Meta.quot . Het verschil onderzoeken is echter informatief over hoe Julia-expressies en macro's werken.

Het verschil tussen Meta.quot en QuoteNode , uitgelegd

Hier is een vuistregel:

  • Gebruik Meta.quot als u interpolatie nodig hebt of wilt ondersteunen;
  • Gebruik QuoteNode als u interpolatie niet kunt of wilt toestaan.

Kortom, het verschil is dat Meta.quot interpolatie binnen het geciteerde ding QuoteNode , terwijl QuoteNode zijn argument beschermt tegen elke interpolatie. Om interpolatie te begrijpen, is het belangrijk om de $ -uitdrukking te vermelden. Er is een soort uitdrukking in Julia die een $ uitdrukking wordt genoemd. Deze uitdrukkingen laten ontsnappen toe. Overweeg bijvoorbeeld de volgende uitdrukking:

julia> ex = :( x = 1; :($x + $x) )
quote 
    x = 1
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

Wanneer geëvalueerd, evalueert deze uitdrukking 1 en wijst deze toe aan x , en construeert vervolgens een uitdrukking in de vorm _ + _ waarbij de _ wordt vervangen door de waarde van x . Het resultaat hiervan moet dus de uitdrukking 1 + 1 (die nog niet is geëvalueerd en zo verschilt van de waarde 2 ). Dit is inderdaad het geval:

julia> eval(ex)
:(1 + 1)

Laten we zeggen dat we een macro schrijven om dit soort expressies te maken. Onze macro heeft een argument nodig, dat de 1 in de ex hierboven zal vervangen. Dit argument kan natuurlijk elke uitdrukking zijn. Hier is iets dat niet helemaal is wat we willen:

julia> macro makeex(arg)
           quote
               :( x = $(esc($arg)); :($x + $x) )
           end
       end
@makeex (macro with 1 method)

julia> @makeex 1
quote 
    x = $(Expr(:escape, 1))
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

julia> @makeex 1 + 1
quote 
    x = $(Expr(:escape, 2))
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

Het tweede geval is onjuist, omdat we 1 + 1 geëvalueerd moeten houden. We lossen dat op door het argument met Meta.quot :

julia> macro makeex2(arg)
           quote
               :( x = $$(Meta.quot(arg)); :($x + $x) )
           end
       end
@makeex2 (macro with 1 method)

julia> @makeex2 1 + 1
quote 
    x = 1 + 1
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

Macrohygiëne is niet van toepassing op de inhoud van een offerte, dus in dit geval is ontsnappen niet nodig (en in dit geval zelfs niet legaal).

Zoals eerder vermeld, staat Meta.quot interpolatie toe. Laten we dat eens proberen:

julia> @makeex2 1 + $(sin(1))
quote 
    x = 1 + 0.8414709848078965
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

julia> let q = 0.5
           @makeex2 1 + $q
       end
quote 
    x = 1 + 0.5
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

Uit het eerste voorbeeld zien we dat interpolatie ons in staat stelt om de sin(1) te onderstrepen, in plaats van dat de uitdrukking een letterlijke sin(1) . Het tweede voorbeeld laat zien dat deze interpolatie gebeurt in het macro-aanroepbereik, niet in het eigen bereik van de macro. Dat komt omdat onze macro geen code heeft geëvalueerd; alles wat het doet is code genereren. De evaluatie van de code (die zijn weg vindt naar de uitdrukking) wordt uitgevoerd wanneer de uitdrukking die de macro genereert daadwerkelijk wordt uitgevoerd.

Wat als we in plaats daarvan QuoteNode hadden gebruikt? Zoals u misschien wel raadt, aangezien QuoteNode interpolatie voorkomt, betekent dit dat het niet werkt.

julia> macro makeex3(arg)
           quote
               :( x = $$(QuoteNode(arg)); :($x + $x) )
           end
       end
@makeex3 (macro with 1 method)

julia> @makeex3 1 + $(sin(1))
quote 
    x = 1 + $(Expr(:$, :(sin(1))))
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

julia> let q = 0.5
           @makeex3 1 + $q
       end
quote 
    x = 1 + $(Expr(:$, :q))
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

julia> eval(@makeex3 $(sin(1)))
ERROR: unsupported or misplaced expression $
 in eval(::Module, ::Any) at ./boot.jl:234
 in eval(::Any) at ./boot.jl:233

In dit voorbeeld kunnen we het erover eens zijn dat Meta.quot meer flexibiliteit biedt, omdat het interpolatie mogelijk maakt. Dus waarom zouden we ooit overwegen om QuoteNode ? In sommige gevallen verlangen we misschien niet echt naar interpolatie en willen we eigenlijk de letterlijke uitdrukking $ . Wanneer zou dat wenselijk zijn? Laten we een generalisatie van @makeex waar we aanvullende argumenten kunnen doorgeven die bepalen wat links en rechts van het + -teken komt:

julia> macro makeex4(expr, left, right)
           quote
               quote
                   $$(Meta.quot(expr))
                   :($$$(Meta.quot(left)) + $$$(Meta.quot(right)))
               end
           end
       end
@makeex4 (macro with 1 method)

julia> @makeex4 x=1 x x
quote  # REPL[110], line 4:
    x = 1 # REPL[110], line 5:
    $(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end

julia> eval(ans)
:(1 + 1)

Een beperking van onze implementatie van @makeex4 is dat we expressies niet als de linker- en rechterkant van de expressie kunnen gebruiken, omdat ze geïnterpoleerd worden. Met andere woorden, de expressies kunnen worden geëvalueerd voor interpolatie, maar we willen ze misschien behouden. (Aangezien er hier veel niveaus van citeren en evalueren zijn, laten we het verduidelijken: onze macro genereert code die een expressie construeert die na evaluatie een andere expressie produceert. Pff!)

julia> @makeex4 x=1 x/2 x
quote  # REPL[110], line 4:
    x = 1 # REPL[110], line 5:
    $(Expr(:quote, :($(Expr(:$, :(x / 2))) + $(Expr(:$, :x)))))
end

julia> eval(ans)
:(0.5 + 1)

We moeten de gebruiker de mogelijkheid geven om te specificeren wanneer interpolatie moet plaatsvinden en wanneer niet. Theoretisch is dat een gemakkelijke oplossing: we kunnen gewoon een van de $ -tekens in onze applicatie verwijderen en de gebruiker zijn eigen bijdrage laten leveren. Dit betekent dat we een geciteerde versie van de uitdrukking die door de gebruiker is ingevoerd (die we al eerder hebben geciteerd en geïnterpoleerd) interpoleren. Dit leidt tot de volgende code, die in het begin wat verwarrend kan zijn vanwege de meerdere geneste niveaus van citeren en citeren. Probeer te lezen en te begrijpen waar elke ontsnapping voor is.

julia> macro makeex5(expr, left, right)
           quote
               quote
                   $$(Meta.quot(expr))
                   :($$(Meta.quot($(Meta.quot(left)))) + $$(Meta.quot($(Meta.quot(right)))))
               end
           end
       end
@makeex5 (macro with 1 method)

julia> @makeex5 x=1 1/2 1/4
quote  # REPL[121], line 4:
    x = 1 # REPL[121], line 5:
    $(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end

julia> eval(ans)
:(1 / 2 + 1 / 4)

julia> @makeex5 y=1 $y $y
ERROR: UndefVarError: y not defined

Het begon goed, maar er is iets misgegaan. De gegenereerde code van de macro probeert de kopie van y in het macro-aanroepbereik te interpoleren; maar er is geen kopie van y in het macro-aanroepbereik. Onze fout maakt interpolatie mogelijk met de tweede en derde argumenten in de macro. Om deze fout te verhelpen, moeten we QuoteNode .

julia> macro makeex6(expr, left, right)
           quote
               quote
                   $$(Meta.quot(expr))
                   :($$(Meta.quot($(QuoteNode(left)))) + $$(Meta.quot($(QuoteNode(right)))))
               end
           end
       end
@makeex6 (macro with 1 method)

julia> @makeex6 y=1 1/2 1/4
quote  # REPL[129], line 4:
    y = 1 # REPL[129], line 5:
    $(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 / 2)))))) + $(Expr(:$, :($(Expr(:quote, :(1 / 4)))))))))
end

julia> eval(ans)
:(1 / 2 + 1 / 4)

julia> @makeex6 y=1 $y $y
quote  # REPL[129], line 4:
    y = 1 # REPL[129], line 5:
    $(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end

julia> eval(ans)
:(1 + 1)

julia> @makeex6 y=1 1+$y $y
quote  # REPL[129], line 4:
    y = 1 # REPL[129], line 5:
    $(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :(1 + $(Expr(:$, :y)))))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end

julia> @makeex6 y=1 $y/2 $y
quote  # REPL[129], line 4:
    y = 1 # REPL[129], line 5:
    $(Expr(:quote, :($(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)) / 2)))))) + $(Expr(:$, :($(Expr(:quote, :($(Expr(:$, :y)))))))))))
end

julia> eval(ans)
:(1 / 2 + 1)

Door QuoteNode , hebben we onze argumenten beschermd tegen interpolatie. Omdat QuoteNode alleen het effect van extra beveiligingen heeft, is het nooit schadelijk om QuoteNode te gebruiken, tenzij u interpolatie wenst. Het begrip van het verschil maakt het echter mogelijk om te begrijpen waar en waarom Meta.quot een betere keuze zou kunnen zijn.

Deze lange oefening is met een voorbeeld dat duidelijk te complex is om in een redelijke toepassing te verschijnen. Daarom hebben we de eerder genoemde vuistregel gerechtvaardigd:

  • Gebruik Meta.quot als u interpolatie nodig hebt of wilt ondersteunen;
  • Gebruik QuoteNode als u interpolatie niet kunt of wilt toestaan.

Hoe zit het met Expr (: quote)?

Expr(:quote, x) is gelijk aan Meta.quot(x) . Dit laatste is echter idiomatischer en heeft de voorkeur. Voor code die veel metaprogrammering gebruikt, wordt vaak een regel using Base.Meta gebruikt, waarmee Meta.quot eenvoudigweg quot kan worden genoemd.

Gids

π's metaprogrammering bits & bobs

doelen:

  • Leer door middel van minimale gerichte functionele / nuttige / niet-abstracte voorbeelden (bijv. @swap of @assert ) die concepten introduceren in geschikte contexten

  • Laat de code liever de concepten illustreren / demonstreren in plaats van paragrafen met uitleg

  • Koppel 'verplicht lezen' niet aan andere pagina's - het onderbreekt het verhaal

  • Presenteer dingen in een verstandige volgorde die het leren het gemakkelijkst maakt

Middelen:

julialang.org
wikibook (@Cormullion)
5 lagen (Leah Hanson)
SO-Doc Quoting (@TotalVerb)
SO-Doc - Symbolen die geen wettelijke identificatie zijn (@TotalVerb)
SO: Wat is een symbool in Julia (@StefanKarpinski)
Discoursthread (@ pi-) Metaprogrammering

Het meeste materiaal is afkomstig van het discourskanaal, het meeste is afkomstig van fcard ... prik me alsjeblieft als ik attributies was vergeten.

Symbool

julia> mySymbol = Symbol("myName")  # or 'identifier'
:myName

julia> myName = 42
42

julia> mySymbol |> eval  # 'foo |> bar' puts output of 'foo' into 'bar', so 'bar(foo)'
42

julia> :( $mySymbol = 1 ) |> eval
1

julia> myName
1

Vlaggen doorgeven aan functies:

function dothing(flag)
  if flag == :thing_one
    println("did thing one")
  elseif flag == :thing_two
    println("did thing two")
  end
end
julia> dothing(:thing_one)
did thing one

julia> dothing(:thing_two)
did thing two

Een hashkey-voorbeeld:

number_names = Dict{Symbol, Int}()
number_names[:one] = 1
number_names[:two] = 2
number_names[:six] = 6

(Geavanceerd) (@fcard) :foo aka :(foo) levert een symbool op als foo een geldige identificatie is, anders een uitdrukking.

# NOTE: Different use of ':' is:
julia> :mySymbol = Symbol('hello world')

#(You can create a symbol with any name with Symbol("<name>"), 
# which lets us create such gems as:
julia> one_plus_one = Symbol("1 + 1")
Symbol("1 + 1")

julia> eval(one_plus_one)
ERROR: UndefVarError: 1 + 1 not defined
...

julia> valid_math = :($one_plus_one = 3)
:(1 + 1 = 3)

julia> one_plus_one_plus_two = :($one_plus_one + 2)
:(1 + 1 + 2)

julia> eval(quote
           $valid_math
           @show($one_plus_one_plus_two)
       end)
1 + 1 + 2 = 5
...

In principe kun je symbolen als lichtgewicht tekenreeksen behandelen. Daar zijn ze niet voor, maar u kunt het, dus waarom niet. Julia's Base doet het zelf, print_with_color(:red, "abc") drukt een roodgekleurd abc af.

Uitdr (AST)

(Bijna) alles in Julia is een expressie, dat wil zeggen een instantie van Expr , die een AST bevat .

# when you type ...
julia> 1+1
2

# Julia is doing: eval(parse("1+1"))
# i.e. First it parses the string "1+1" into an `Expr` object ...
julia> ast = parse("1+1")
:(1 + 1)

# ... which it then evaluates:
julia> eval(ast)
2

# An Expr instance holds an AST (Abstract Syntax Tree).  Let's look at it:
julia> dump(ast)
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 1
    3: Int64 1
  typ: Any
  
# TRY: fieldnames(typeof(ast))
 
julia>      :(a + b*c + 1)  ==
       parse("a + b*c + 1") ==
       Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1)
true

Nesting Expr s:

julia> dump( :(1+2/3) )
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol /
        2: Int64 2
        3: Int64 3
      typ: Any
  typ: Any
  
# Tidier rep'n using s-expr
julia> Meta.show_sexpr( :(1+2/3) ) 
(:call, :+, 1, (:call, :/, 2, 3))

multiline Expr s met quote

julia> blk = quote
           x=10
           x+1
       end
quote  # REPL[121], line 2:
    x = 10 # REPL[121], line 3:
    x + 1
end

julia> blk == :( begin  x=10; x+1  end )
true

# Note: contains debug info:
julia> Meta.show_sexpr(blk)
(:block,
  (:line, 2, Symbol("REPL[121]")),
  (:(=), :x, 10),
  (:line, 3, Symbol("REPL[121]")),
  (:call, :+, :x, 1)
)

# ... unlike:
julia> noDbg = :( x=10; x+1 ) 
quote 
    x = 10
    x + 1
end

... dus quote is functioneel hetzelfde maar biedt extra debug-info.

(*) TIP : gebruik let om x binnen het blok te houden

quote -ing een quote

Expr(:quote, x) wordt gebruikt om aanhalingstekens binnen aanhalingstekens weer te geven.

Expr(:quote, :(x + y)) == :(:(x + y))

Expr(:quote, Expr(:$, :x)) == :(:($x))

QuoteNode(x) lijkt op Expr(:quote, x) maar het voorkomt interpolatie.

eval(Expr(:quote, Expr(:$, 1))) == 1

eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)

( Onderscheid de verschillende citatiemechanismen in Julia metaprogrammering

Zijn $ en : (…) op de een of andere manier omgekeerd van elkaar?

:(foo) betekent "kijk niet naar de waarde, kijk naar de uitdrukking" $foo betekent "verander de uitdrukking in zijn waarde"

:($(foo)) == foo . $(:(foo)) is een fout. $(...) is geen operatie en doet zelf niets, het is een "interpoleer dit!" teken dat de citaatsyntaxis gebruikt. ie Het bestaat alleen binnen een offerte.

Is $ foo hetzelfde als eval( foo ) ?

Nee! $foo wordt ingeruild voor de compile-time waarde eval(foo) betekent om dat tijdens runtime te doen

eval zal optreden in de globale reikwijdte interpolatie is lokaal

eval(:<expr>) moet hetzelfde retourneren als alleen <expr> (ervan uitgaande dat <expr> een geldige uitdrukking is in de huidige globale ruimte)

eval(:(1 + 2)) == 1 + 2

eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end

macro s

Klaar? :)

# let's try to make this!
julia> x = 5; @show x;
x = 5

Laten we onze eigen @show macro maken:

macro log(x)
  :(
    println( "Expression: ", $(string(x)), " has value: ", $x )
  )
end

u = 42
f = x -> x^2
@log(u)       # Expression: u has value: 42
@log(42)      # Expression: 42 has value: 42
@log(f(42))   # Expression: f(42) has value: 1764
@log(:u)      # Expression: :u has value: u

expand om een Expr

5 lagen (Leah Hanson) <- legt uit hoe Julia de broncode als een tekenreeks neemt, deze tokeniseert in een Expr -tree (AST), alle macro's uitbreidt (nog steeds AST), verlaagt (verlaagde AST) en converteert vervolgens naar LLVM (en verder - op dit moment hoeven we ons geen zorgen te maken over wat daarachter ligt!)

V: code_lowered werkt in op functies. Is het mogelijk om een Expr te verlagen? A: jep!

# function -> lowered-AST
julia> code_lowered(*,(String,String))
1-element Array{LambdaInfo,1}:
 LambdaInfo template for *(s1::AbstractString, ss::AbstractString...) at strings/basic.jl:84

# Expr(i.e. AST) -> lowered-AST
julia> expand(:(x ? y : z))
:(begin
        unless x goto 3
        return y
        3:
        return z
    end)

julia> expand(:(y .= x.(i)))
:((Base.broadcast!)(x,y,i))

# 'Execute' AST or lowered-AST
julia> eval(ast)

Als u alleen macro's wilt uitbreiden, kunt u macroexpand :

# AST -> (still nonlowered-)AST but with macros expanded:
julia> macroexpand(:(@show x))
quote
    (Base.println)("x = ",(Base.repr)(begin  # show.jl, line 229:
                #28#value = x
            end))
    #28#value
end

... die een niet-verlaagde AST retourneert, maar met alle macro's uitgebreid.

esc()

esc(x) geeft een Uitdr terug die zegt: "Pas hier geen hygiëne op toe", dit is hetzelfde als Expr(:escape, x) . Hygiëne is wat een macro op zichzelf houdt, en je esc dingen als je wilt dat ze "lekken". bv

Voorbeeld: macro swap om esc() te illustreren

macro swap(p, q)
  quote
    tmp = $(esc(p))
    $(esc(p)) = $(esc(q))
    $(esc(q)) = tmp
  end
end

x,y = 1,2
@swap(x,y)
println(x,y)  # 2 1

$ kunnen we het quote 'ontsnappen'. Dus waarom niet gewoon $p en $q ? d.w.z

    # FAIL!
    tmp = $p
    $p = $q
    $q = tmp

Omdat dat eerst naar het macro voor p zou kijken, en het zou een lokale p dwz de parameter p (ja, als u vervolgens p zonder esc toegang opent, beschouwt de macro de p parameter als een lokale variabele).

Dus $p = ... is gewoon een toewijzing aan de lokale p . het heeft geen invloed op de variabele die in de aanroepcontext is doorgegeven.

Ok dus hoe zit het met:

    # Almost!
    tmp = $p          # <-- you might think we don't 
    $(esc(p)) = $q    #       need to esc() the RHS
    $(esc(q)) = tmp

Dus 'lek' p esc(p) p in de aanroepende context. "Het ding dat werd doorgegeven in de macro die we ontvangen als p "

julia> macro swap(p, q)                  
         quote                           
           tmp = $p                      
           $(esc(p)) = $q                
           $(esc(q)) = tmp               
         end                             
       end                               
@swap (macro with 1 method)              

julia> x, y = 1, 2                       
(1,2)                                    

julia> @swap(x, y);                      

julia> @show(x, y);                      
x = 2                                    
y = 1                                    

julia> macroexpand(:(@swap(x, y)))       
quote  # REPL[34], line 3:               
    #10#tmp = x # REPL[34], line 4:      
    x = y # REPL[34], line 5:            
    y = #10#tmp                          
end                                      

Zoals je kunt zien krijgt tmp de hygiënische behandeling #10#tmp , terwijl x en y dat niet doen. Julia maakt een unieke identificatie voor tmp , iets wat je handmatig kunt doen met gensym , namelijk:

julia> gensym(:tmp)
Symbol("##tmp#270")

Maar: er is een gotcha:

julia> module Swap
       export @swap

       macro swap(p, q)
         quote
           tmp = $p
           $(esc(p)) = $q
           $(esc(q)) = tmp
         end
       end
       end
Swap

julia> using Swap

julia> x,y = 1,2
(1,2)

julia> @swap(x,y)
ERROR: UndefVarError: x not defined

Iets anders wat julia's macrohygiëne doet is, als de macro uit een andere module komt, maakt het alle variabelen (die niet waren toegewezen in de terugkerende expressie van de macro, zoals in dit geval tmp ), globalen van de huidige module, dus $p wordt Swap.$p , eveneens $q -> Swap.$q .

Over het algemeen moet u esc als u een variabele nodig hebt die buiten het bereik van de macro valt, dus esc(p) en esc(q) ongeacht of deze zich op de LHS of RHS van een expressie bevinden, of zelfs alleen.

mensen hebben gensym sa al een paar keer genoemd en binnenkort zul je verleid worden door de donkere kant van het verzuim om de hele uitdrukking te ontsnappen met een paar gensym hier en daar doorspekt, maar ... Zorg ervoor dat je begrijpt hoe hygiëne werkt voordat je probeert slimmer dan het! Het is geen bijzonder complex algoritme, dus het moet niet te lang duren, maar haast je niet! Gebruik die kracht niet totdat je alle gevolgen ervan begrijpt ... (@fcard)

Voorbeeld: until macro

(@ Ismael-VC)

"until loop"
macro until(condition, block)
    quote
        while ! $condition
            $block
        end
    end |> esc
end

julia> i=1;  @until(  i==5,  begin; print(i); i+=1; end  )
1234

(@fcard) |> is echter controversieel. Het verbaast me dat een menigte nog niet is komen ruziën. (misschien is iedereen het gewoon beu). Er is een aanbeveling om de meeste, zo niet alle macro's slechts een aanroep van een functie te laten zijn, dus:

macro until(condition, block)
    esc(until(condition, block))
end

function until(condition, block)
    quote
        while !$condition
            $block
        end
    end
end

... is een veiliger alternatief.

## @ fcard's eenvoudige macro-uitdaging

Taak: Wissel de operanden om, zodat swaps(1/2) 2.00 geeft, dwz 2/1

macro swaps(e)
    e.args[2:3] = e.args[3:-1:2]   
    e
end
@swaps(1/2)
2.00

Meer macro-uitdagingen van @fcard hier


Interpolatie en assert macro

http://docs.julialang.org/en/release-0.5/manual/metaprogramming/#building-an-advanced-macro

macro assert(ex)
    return :( $ex ? nothing : throw(AssertionError($(string(ex)))) )
end

Vraag: Waarom de laatste $ ? A: Het interpoleert, dwz dwingt Julia om die string(ex) te eval wanneer de uitvoering de aanroep van deze macro passeert. dat wil zeggen als u die code uitvoert, zal deze geen evaluatie afdwingen. Maar op het moment dat je assert(foo) zal Julia deze macro gebruiken en het 'AST-token / Expr' vervangen door wat het retourneert, en de $ zal in actie treden .

Een leuke hack voor het gebruik van {} voor blokken

(@fcard) Ik denk niet dat er iets technisch is waardoor {} niet als blokken kan worden gebruikt, in feite kan men zelfs de resterende {} syntaxis gebruiken om het te laten werken:

julia> macro c(block)
         @assert block.head == :cell1d
         esc(quote
           $(block.args...)
         end)
       end
@c (macro with 1 method)

julia> @c {
         print(1)
         print(2)
         1+2
       }
123

* (werkt waarschijnlijk nog steeds niet als / wanneer de syntaxis {} wordt hergebruikt)


Dus eerst ziet Julia het macro-token, dus het zal tokens lezen / ontleden tot het overeenkomende end , en wat maken? Een Expr met .head=:macro of iets? Slaat het "a+1" als een string of splitst het het op in :+(:a, 1) ? Hoe te bekijken?

?

(@fcard) In dit geval vanwege een lexicaal bereik, is a niet gedefinieerd in het @M dus gebruikt het de globale variabele ... Ik vergat eigenlijk om te ontsnappen aan de flipplin-uitdrukking in mijn domme voorbeeld, maar de "werkt alleen binnen de dezelfde module ' is nog steeds van toepassing.

julia> module M
       macro m()
         :(a+1)
       end
       end
M

julia> a = 1
1

julia> M.@m
ERROR: UndefVarError: a not defined

De reden hiervoor is dat, als de macro wordt gebruikt in een andere module dan de module waarin deze is gedefinieerd, alle variabelen die niet zijn gedefinieerd in de uit te breiden code worden behandeld als globalen van de macro's module.

julia> macroexpand(:(M.@m))
:(M.a + 1)

GEAVANCEERD

### @ Ismael-VC

@eval begin
    "do-until loop"
    macro $(:do)(block, until::Symbol, condition)
        until ≠ :until && 
            error("@do expected `until` got `$until`")
        quote
            let
                $block
                @until $condition begin
                    $block
                end
            end
        end |> esc
    end
end
julia> i = 0            
0                       

julia> @do begin        
           @show i      
           i += 1       
       end until i == 5 
i = 0                   
i = 1                   
i = 2                   
i = 3                   
i = 4

Scott's macro:

"""
Internal function to return captured line number information from AST

##Parameters
- a:     Expression in the julia type Expr

##Return

- Line number in the file where the calling macro was invoked
"""
_lin(a::Expr) = a.args[2].args[1].args[1]

"""
Internal function to return captured file name information from AST

##Parameters
- a:     Expression in the julia type Expr

##Return
- The name of the file where the macro was invoked
"""
_fil(a::Expr) = string(a.args[2].args[1].args[2])

"""
Internal function to determine if a symbol is a status code or variable
"""
function _is_status(sym::Symbol)
    sym in (:OK, :WARNING, :ERROR) && return true
    str = string(sym)
    length(str) > 4 && (str[1:4] == "ERR_" || str[1:5] == "WARN_" || str[1:5] == "INFO_")
end

"""
Internal function to return captured error code from AST

##Parameters
- a:     Expression in the julia type Expr

##Return
- Error code from the captured info in the AST from the calling macro
"""
_err(a::Expr) =
    (sym = a.args[2].args[2] ; _is_status(sym) ? Expr(:., :Status, QuoteNode(sym)) : sym)

"""
Internal function to produce a call to the log function based on the macro arguments and the AST from the ()->ERRCODE anonymous function definition used to capture error code, file name and line number where the macro is used

##Parameters
- level:     Loglevel which has to be logged with macro
- a:         Expression in the julia type Expr
- msgs:      Optional message

##Return
- Statuscode
"""
function _log(level, a, msgs)
    if isempty(msgs)
        :( log($level, $(esc(:Symbol))($(_fil(a))), $(_lin(a)), $(_err(a)) )
    else
        :( log($level, $(esc(:Symbol))($(_fil(a))), $(_lin(a)), $(_err(a)), message=$(esc(msgs[1]))) )
    end
end

macro warn(a, msgs...)  ; _log(Warning, a, msgs) ; end

ongewenste / onbewerkte ...

een macro bekijken / dumpen

(@ pi-) Stel dat ik gewoon macro m(); a+1; end doe macro m(); a+1; end in een nieuwe REPL. Zonder a gedefinieerd. Hoe kan ik het 'bekijken'? zoals, is er een manier om een macro te 'dumpen'? Zonder het daadwerkelijk uit te voeren

(@fcard) Alle code in macro's worden in feite in functies geplaatst, zodat u alleen de verlaagde of door type afgeleide code kunt bekijken.

julia> macro m()  a+1  end
@m (macro with 1 method)

julia> @code_typed @m
LambdaInfo for @m()
:(begin 
        return Main.a + 1
    end)

julia> @code_lowered @m  
CodeInfo(:(begin
        nothing
        return Main.a + 1
    end))
# ^ or: code_lowered(eval(Symbol("@m")))[1] # ouf!

Andere manieren om de functie van een macro te krijgen:

julia> macro getmacro(call) call.args[1] end
@getmacro (macro with 1 method)

julia> getmacro(name) = getfield(current_module(), name.args[1])
getmacro (generic function with 1 method)

julia> @getmacro @m
@m (macro with 1 method)

julia> getmacro(:@m)
@m (macro with 1 method)
julia> eval(Symbol("@M"))
@M (macro with 1 method)

julia> dump( eval(Symbol("@M")) )
@M (function of type #@M)

julia> code_typed( eval(Symbol("@M")) )
1-element Array{Any,1}:
 LambdaInfo for @M()

julia> code_typed( eval(Symbol("@M")) )[1]
LambdaInfo for @M()
:(begin 
        return $(Expr(:copyast, :($(QuoteNode(:(a + 1))))))
    end::Expr)

julia> @code_typed @M
LambdaInfo for @M()
:(begin 
        return $(Expr(:copyast, :($(QuoteNode(:(a + 1))))))
    end::Expr)

^ lijkt erop dat ik in plaats daarvan code_typed kan gebruiken

Hoe eval(Symbol("@M")) te begrijpen?

(@fcard) Aan elke macro is momenteel een functie gekoppeld. Als u een macro met de naam M , wordt de functie van de macro @M . Over het algemeen kunt u de waarde van een functie verkrijgen met bijvoorbeeld eval(:print) maar met de functie van een macro moet u Symbol("@M") , omdat alleen :@M een Expr(:macrocall, Symbol("@M")) en evalueren dat een macro-uitbreiding veroorzaakt.

Waarom geeft code_typed geen params weer?

(@pi-)

julia> code_typed( x -> x^2 )[1]
LambdaInfo for (::##5#6)(::Any)
:(begin 
        return x ^ 2
    end)

^ hier zie ik er een ::Any parameter, maar deze lijkt niet te zijn verbonden met het token x .

 julia> code_typed( print )[1]
LambdaInfo for print(::IO, ::Char)
:(begin 
        (Base.write)(io,c)
        return Base.nothing
    end::Void)

^ evenzo hier; er is niets dat io verbindt met de ::IO Dus dit kan toch geen complete dump zijn van de AST-weergave van die specifieke print ...?

(@fcard) print(::IO, ::Char) vertelt je alleen welke methode het is, het maakt geen deel uit van de AST. Het is zelfs niet meer aanwezig in master:

julia> code_typed(print)[1]
CodeInfo(:(begin
        (Base.write)(io,c)
        return Base.nothing
    end))=>Void

(@ pi-) Ik begrijp niet wat je daarmee bedoelt. Het lijkt de AST te dumpen voor het lichaam van die methode, niet? Ik dacht dat code_typed de AST geeft voor een functie. Maar het lijkt de eerste stap te missen, namelijk het instellen van tokens voor params.

(@fcard) code_typed is bedoeld om alleen de AST van het lichaam te tonen, maar voor nu geeft het wel de volledige AST van de methode, in de vorm van een LambdaInfo (0,5) of CodeInfo (0,6), maar veel informatie wordt weggelaten wanneer afgedrukt naar de repl. U moet het veld LambdaInfo per veld inspecteren om alle details te krijgen. dump zal je repl overspoelen, dus je zou kunnen proberen:

macro method_info(call)
  quote
    method = @code_typed $(esc(call))
    print_info_fields(method)
  end
end

function print_info_fields(method)
  for field in fieldnames(typeof(method))
    if isdefined(method, field) && !(field in [Symbol(""), :code])
      println("  $field = ", getfield(method, field))
    end
  end
  display(method)
end

print_info_fields(x::Pair) = print_info_fields(x[1])

Die alle waarden van de genoemde velden van de AST van een methode geeft:

julia> @method_info print(STDOUT, 'a')
  rettype = Void
  sparam_syms = svec()
  sparam_vals = svec()
  specTypes = Tuple{Base.#print,Base.TTY,Char}
  slottypes = Any[Base.#print,Base.TTY,Char]
  ssavaluetypes = Any[]
  slotnames = Any[Symbol("#self#"),:io,:c]
  slotflags = UInt8[0x00,0x00,0x00]
  def = print(io::IO, c::Char) at char.jl:45
  nargs = 3
  isva = false
  inferred = true
  pure = false
  inlineable = true
  inInference = false
  inCompile = false
  jlcall_api = 0
  fptr = Ptr{Void} @0x00007f7a7e96ce10
LambdaInfo for print(::Base.TTY, ::Char)
:(begin
        $(Expr(:invoke, LambdaInfo for write(::Base.TTY, ::Char), :(Base.write), :(io), :(c)))
        return Base.nothing
    end::Void)

Zie de lil ' def = print(io::IO, c::Char) ? Daar ga je! (ook de slotnames = [..., :io, :c] deel) Ook ja, het verschil in output is omdat ik de resultaten liet zien op master.

???

(@ Ismael-VC) bedoel je zo? Algemene verzending met symbolen

Je kunt het op deze manier doen:

julia> function dispatchtest{alg}(::Type{Val{alg}})
           println("This is the generic dispatch. The algorithm is $alg")
       end
dispatchtest (generic function with 1 method)

julia> dispatchtest(alg::Symbol) = dispatchtest(Val{alg})
dispatchtest (generic function with 2 methods)

julia> function dispatchtest(::Type{Val{:Euler}})
           println("This is for the Euler algorithm!")
       end
dispatchtest (generic function with 3 methods)

julia> dispatchtest(:Foo)
This is the generic dispatch. The algorithm is Foo

julia> dispatchtest(:Euler)

Dit is voor het Euler-algoritme! Ik vraag me af wat @fcard denkt over generieke symboolverzending! --- ^: engel:

Module Gotcha

@def m begin
  a+2
end

@m # replaces the macro at compile-time with the expression a+2

Nauwkeuriger, werkt alleen binnen het topniveau van de module waarin de macro werd gedefinieerd.

julia> module M
       macro m1()
         a+1
       end
       end
M

julia> macro m2()
         a+1
       end
@m2 (macro with 1 method)

julia> a = 1
1

julia> M.@m1
ERROR: UndefVarError: a not defined

julia> @m2
2

julia> let a = 20
         @m2
       end
2

esc dat dit gebeurt, maar standaardinstelling is altijd in strijd met het taalontwerp. Een goede verdediging hiervoor is om te voorkomen dat iemand namen in macro's gebruikt en introduceert, waardoor ze moeilijk te volgen zijn voor een menselijke lezer.

Python `dict` / JSON zoals syntaxis voor` Dict` -literalen.

Invoering

Julia gebruikt de volgende syntaxis voor woordenboeken:

Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)

Terwijl Python en JSON er zo uitzien:

{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}

Ter illustratie kunnen we deze syntaxis ook gebruiken in Julia en er nieuwe semantiek aan toevoegen ( Dict syntaxis is de idiomatische manier in Julia, die wordt aanbevolen).

Laten we eerst kijken wat voor soort uitdrukking het is:

julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))

Dit betekent dat we dit moeten nemen :cell1d expressie en deze transformeren of een nieuwe expressie retourneren die er als volgt zou moeten uitzien:

julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))

Macro definitie

De volgende macro, hoewel eenvoudig, maakt het mogelijk dergelijke codegeneratie en -transformatie aan te tonen:

macro dict(expr)
    # Check the expression has the correct form:
    if expr.head ≠ :cell1d || any(sub_expr.head ≠ :(:) for sub_expr ∈ expr.args)
        error("syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`")
    end

    # Create empty `:Dict` expression which will be returned:
    block = Expr(:call, :Dict)    # :(Dict())

    # Append `(key => value)` pairs to the block:
    for pair in expr.args
        k, v = pair.args
        push!(block.args, :($k => $v))
    end    # :(Dict(k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ))

    # Block is escaped so it can reach variables from it's calling scope:
    return esc(block)
end

Laten we de resulterende macro-uitbreiding eens bekijken:

julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))

Gebruik

julia> @dict {"a": :b, 'c': 1, :d: 2.0}
Dict{Any,Any} with 3 entries:          
  "a" => :b                            
  :d  => 2.0                           
  'c' => 1                             

julia> @dict {                      
           "string": :b,            
           'c'     : 1,             
           :symbol : π,             
           Function: print,         
           (1:10)  : range(1, 10)   
       }                            
Dict{Any,Any} with 5 entries:       
  1:10     => 1:10                  
  Function => print                 
  "string" => :b                    
  :symbol  => π = 3.1415926535897...
  'c'      => 1         

Het laatste voorbeeld is exact gelijk aan:

Dict(                      
    "string" => :b,            
    'c'      => 1,             
    :symbol  => π,             
    Function => print,         
    (1:10)   => range(1, 10)   
)

misusage

julia> @dict {"one": 1, "two": 2, "three": 3, "four": 4, "five" => 5}
syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`

julia> @dict ["one": 1, "two": 2, "three": 3, "four": 4, "five" => 5]
syntax: expected `{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}`

Merk op dat Julia heeft ook andere toepassingen voor colon : als zodanig je nodig hebt om het bereik letterlijke uitdrukkingen wrap met haakjes of gebruik de range -functie, bijvoorbeeld.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow