Sök…


Syntax

  • makronamn (ex) ... slut
  • offert ... slut
  • : (...)
  • $ x
  • Meta.quot (x)
  • QuoteNode (x)
  • esc (x)

Anmärkningar

Julias metaprogrammeringsfunktioner är starkt inspirerade av de från Lisp-liknande språk och kommer att verka bekanta för dem med lite Lisp-bakgrund. Metaprogrammering är mycket kraftfull. När den används korrekt kan det leda till en mer kortfattad och läsbar kod.

quote ... end är kvasakvotsyntax. I stället för uttryck som utvärderas, analyseras de helt enkelt. Värdet på quote ... end slututtrycket är det resulterande Abstract Syntax Tree (AST).

Syntax :(...) liknar quote ... end syntax, men det är mer lätt. Denna syntax är mer kortfattad än quote ... end .

Inuti en kvasakvot är $ -operatören speciell och interpolerar sitt argument i AST. Argumentet förväntas vara ett uttryck som är skarvad direkt i AST.

Meta.quot(x) citerar sitt argument. Detta är ofta användbart i kombination med att använda $ för interpolering, eftersom det gör att uttryck och symboler kan delas bokstavligen i AST.

Återimplementera @show-makroen

I Julia är @show ofta användbar för felsökning. Det visar både uttrycket som ska utvärderas och dess resultat och slutligen returnerar värdet på resultatet:

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

Det är enkelt att skapa vår egen version av @show :

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

För att använda den nya versionen använder du bara @myshow :

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

julia> x
2

Tills slingan

Vi är alla vana vid while syntax, som kör sin kropp medan villkoret utvärderas till true . Tänk om vi vill implementera en until loop, som kör en slinga tills villkoret utvärderas till true ?

I Julia kan vi göra detta genom att skapa ett @until makro som slutar att köra sin kropp när villkoret är uppfyllt:

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

Här har vi använt funktionen chaining syntax |> , vilket motsvarar esc funktionen på hela quote . esc funktionen förhindrar makrohygien från att applicera på makroinnehållet; utan det kommer variabler som skopas i makroen att bytas namn för att förhindra kollisioner med externa variabler. Se Julia-dokumentationen om makrohygien för mer information.

Du kan använda mer än ett uttryck i den här slingan genom att helt enkelt sätta allt i ett begin ... end slutblock:

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 och Expr (: offert)

Det finns tre sätt att citera något med en Julia-funktion:

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

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

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

Vad betyder "citationstecken" och vad är det bra för? Citering gör att vi kan skydda uttryck från att tolkas som specialformer av Julia. Ett vanligt fall är när vi genererar uttryck som bör innehålla saker som utvärderas till symboler. (Till exempel måste detta makro returnera ett uttryck som utvärderas till en symbol.) Det fungerar inte bara för att returnera symbolen:

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

julia> @mysym
ERROR: UndefVarError: x not defined

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

Vad händer här? @mysym expanderar till :x , som som ett uttryck tolkas som variabeln x . Men inget har tilldelats x ännu, så vi får ett fel som x not defined .

För att komma runt detta måste vi citera resultatet av vår makro:

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

julia> @mysym2
:x

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

Här har vi använt Meta.quot funktionen för att förvandla vår symbol till en citerad symbol, vilket är det resultat vi vill ha.

Vad är skillnaden mellan Meta.quot och QuoteNode , och vilken ska jag använda? I nästan alla fall spelar skillnaden ingen roll. Det är kanske lite säkrare ibland att använda QuoteNode istället för Meta.quot . Att utforska skillnaden är informativt hur Julia-uttryck och makron fungerar.

Skillnaden mellan Meta.quot och QuoteNode , förklaras

Här är en tumregel:

  • Om du behöver eller vill stödja interpolering, använd Meta.quot ;
  • Om du inte kan eller inte vill tillåta interpolering, använd QuoteNode .

Kort sagt är skillnaden att Meta.quot tillåter interpolering inom den citerade saken, medan QuoteNode skyddar sitt argument från alla interpolationer. För att förstå interpolering är det viktigt att nämna $ -uttrycket. Det finns ett slags uttryck i Julia som kallas ett $ -uttryck. Dessa uttryck möjliggör rymning. Tänk till exempel på följande uttryck:

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

När det utvärderas kommer detta uttryck att utvärdera 1 och tilldela det till x , sedan konstruera ett uttryck med formen _ + _ där _ kommer att ersättas med värdet på x . Således skulle resultatet av detta vara uttrycket 1 + 1 (som ännu inte utvärderas och så skiljer sig från värdet 2 ). Detta är faktiskt fallet:

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

Låt oss säga nu att vi skriver ett makro för att bygga upp sådana uttryck. Vår makro kommer att ta ett argument som kommer att ersätta 1 i ex ovan. Detta argument kan naturligtvis vara vilket som helst uttryck. Här är något som vi inte vill:

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

Det andra fallet är felaktigt, eftersom vi borde hålla 1 + 1 obevärderade. Vi fixar det genom att citera argumentet med 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

Makrohygien gäller inte innehållet i en offert, så det är inte nödvändigt att rymma i detta fall (och i själva verket inte lagligt) i det här fallet.

Som nämnts tidigare tillåter Meta.quot interpolering. Så låt oss prova det:

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

Från det första exemplet ser vi att interpolering tillåter oss att inline sin(1) , istället för att uttrycket är en bokstavlig sin(1) . Det andra exemplet visar att denna interpolering sker inom makroinkallationsomfånget, inte makroens eget räckvidd. Det beror på att vår makro faktiskt inte har utvärderat någon kod; allt det gör är att generera kod. Utvärderingen av koden (som tar sig in i uttrycket) görs när uttrycket som makroen genererar verkligen körs.

Tänk om vi hade använt QuoteNode istället? Som ni kan gissa, eftersom QuoteNode förhindrar att interpolering sker alls, betyder det att det inte fungerar.

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

I det här exemplet kan vi komma överens om att Meta.quot ger större flexibilitet eftersom det tillåter interpolering. Så varför kan vi någonsin överväga att använda QuoteNode ? I vissa fall kanske vi faktiskt inte önskar interpolering, och vi vill faktiskt ha det bokstavliga $ -uttrycket. När skulle det vara önskvärt? Låt oss överväga en generalisering av @makeex där vi kan överföra ytterligare argument som avgör vad som kommer till vänster och höger om + -tecknet:

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)

En begränsning av vår implementering av @makeex4 är att vi inte kan använda uttryck som antingen vänster och höger sida av uttrycket direkt, eftersom de interpoleras. Med andra ord kan uttrycka utvärderas för interpolering, men vi kanske vill att de ska bevaras. (Eftersom det finns många nivåer av citat och utvärdering här, låt oss förtydliga: vår makro genererar kod som konstruerar ett uttryck som vid utvärdering ger ett annat uttryck . Phew!)

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)

Vi borde låta användaren specificera när interpolering ska ske och när det inte borde göra det. Teoretiskt sett är det en enkel lösning: vi kan bara ta bort ett av $ -tecknen i vår applikation och låta användaren bidra med sina egna. Vad detta betyder är att vi interpolerar en citerad version av uttrycket som användaren har angett (som vi redan citerat och interpolerat en gång). Detta leder till följande kod, som kan vara lite förvirrande till en början, på grund av de många kapslade nivåerna för citationstecken och uppräkning. Försök att läsa och förstå vad varje flykt är till för.

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

Saker började bra, men något har gått fel. Makroens genererade kod försöker interpolera kopian av y i makroinkallationsomfånget; men det finns ingen kopia av y i makroinkallationsomfånget. Vårt fel är att tillåta interpolering med det andra och det tredje argumentet i makroen. För att fixa detta fel måste vi använda 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)

Genom att använda QuoteNode har vi skyddat våra argument från interpolering. Eftersom QuoteNode endast har effekten av ytterligare skydd är det aldrig skadligt att använda QuoteNode , såvida du inte vill ha interpolering. Att förstå skillnaden gör det dock möjligt att förstå var och varför Meta.quot kan vara ett bättre val.

Denna långa övning är med ett exempel som är helt klart för komplex för att dyka upp i alla rimliga tillämpningar. Därför har vi rättfärdigat följande tumregel, som nämnts tidigare:

  • Om du behöver eller vill stödja interpolering, använd Meta.quot ;
  • Om du inte kan eller inte vill tillåta interpolering, använd QuoteNode .

Vad sägs om Expr (: offert)?

Expr(:quote, x) motsvarar Meta.quot(x) . Den senare är emellertid mer idiomatisk och föredras. För kod som i using Base.Meta grad använder metaprogrammering används ofta en using Base.Meta linje som gör det Meta.quot att hänvisa till quot som helt enkelt quot .

Guide

π: s Metaprogrammeringsbitar & bobs

mål:

  • Lär dig genom minimala målinriktade funktionella / användbara / icke-abstrakta exempel (t.ex. @swap eller @assert ) som introducerar begrepp i lämpliga sammanhang

  • Föredra att låta koden illustrera / demonstrera begreppen snarare än förklarande stycken

  • Undvik att länka "obligatorisk läsning" till andra sidor - det avbryter berättelsen

  • Presentera saker i en förnuftig ordning som underlättar lärandet

Resurser:

julialang.org
wikibook (@Cormullion)
5 lager (Leah Hanson)
SO-Doc-offert (@TotalVerb)
SO-Doc - Symboler som inte är juridiska identifierare (@TotalVerb)
SO: Vad är en symbol i Julia (@StefanKarpinski)
Diskurs tråd (@ pi-) Metaprogrammering

Det mesta av materialet har kommit från diskurskanalen, det mesta har kommit från fcard ... snälla tacka mig om jag hade glömt attribut.

Symbol

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

julia> myName = 42
42

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

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

julia> myName
1

Flytta flaggor till funktioner:

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

Ett hashkey-exempel:

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

(Avancerat) (@fcard) :foo aka :(foo) ger en symbol om foo är en giltig identifierare, annars ett uttryck.

# 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
...

I princip kan du behandla symboler som lätta strängar. Det är inte det de är för, men du kan göra det, så varför inte. Julias Base själv gör det, print_with_color(:red, "abc") skriver ut en rödfärgad abc.

Expr (AST)

(Nästan) allt i Julia är ett uttryck, dvs ett exempel på Expr , som kommer att innehålla en 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))

multiline Expr s med 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

... så quote är funktionellt samma men ger extra felsökningsinfo.

(*) TIPS : Använd let att hålla x inom blocket

quote en quote

Expr(:quote, x) används för att representera offerter inom offert.

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

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

QuoteNode(x) liknar Expr(:quote, x) men det förhindrar interpolering.

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

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

( Ta reda på de olika citatmekanismerna i Julia metaprogrammering

Är $ och : (…) på något sätt inverser av varandra?

:(foo) betyder "inte titta på värdet, titta på uttrycket" $foo betyder "ändra uttrycket till dess värde"

:($(foo)) == foo . $(:(foo)) är ett fel. $(...) är inte en operation och gör inget av sig själv, det är en "interpolera detta!" tecken på att den citerande syntaxen använder. dvs det finns bara inom en offert.

Är $ foo samma sak som eval( foo ) ?

Nej! $foo byts mot kompileringstidsvärde som eval(foo) att göra det vid körning

eval kommer att inträffa i den globala omfattningen interpolering är lokal

eval(:<expr>) ska returnera samma sak som bara <expr> (förutsatt att <expr> är ett giltigt uttryck i det nuvarande globala utrymmet)

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

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

macro s

Redo? :)

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

Låt oss skapa vår egen @show makro:

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 att sänka ett Expr

5 lager (Leah Hanson) <- förklarar hur Julia tar källkoden som en sträng, tokeniserar den till en Expr tre (AST), expanderar ut alla makron (fortfarande AST), sänker (sänks AST) och konverterar sedan till LLVM (och utöver - för tillfället behöver vi inte oroa oss för vad som ligger längre bort!)

F: code_lowered fungerar på funktioner. Är det möjligt att sänka en Expr ? A: yup!

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

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

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

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

Om du bara vill utöka makron kan du använda 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

... som returnerar en icke-sänkt AST men med alla makron utvidgade.

esc()

esc(x) returnerar en Expr som säger "inte tillämpa hygien för detta", det är samma som Expr(:escape, x) . Hygien är det som håller en makro fristående, och du esc saker om du vill att de ska "läcka". t.ex

Exempel: swap makro för att illustrera 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

$ tillåter oss att fly från quote . Så varför inte bara $p och $q ? dvs.

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

Eftersom det först skulle se till macro för p , och det skulle hitta en lokal p dvs. parametern p (ja, om du senare går till p utan att esc , betraktar makro p parametern som en lokal variabel).

$p = ... är bara en tilldelning till den lokala p . det påverkar inte vilken variabel som har skickats in i samtalssammanhanget.

Ok så hur är det med:

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

esc(p) "läcker" p in den samtalskontext. "Det som överfördes till makro som vi får som 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                                      

Som ni ser tmp får hygienbehandlingen #10#tmp , medan x och y inte gör det. Julia skapar en unik identifierare för tmp , något du manuellt kan göra med gensym , dvs.

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

Men: Det finns en 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

En annan sak som julias makrohygien gör är om makroen kommer från en annan modul, den gör alla variabler (som inte tilldelades inuti makroens återkommande uttryck, som tmp i detta fall) globaler i den aktuella modulen, så $p blir Swap.$p , likaså $q -> Swap.$q .

I allmänhet, om du behöver en variabel som ligger utanför makroens räckvidd bör du undvika den, så du bör esc(p) och esc(q) oavsett om de är på LHS eller RHS i ett uttryck, eller ens ensamma.

människor har redan nämnt gensym sa några gånger och snart kommer du att förföras av den mörka sidan av att inte slippa hela uttrycket med några få gensym peppade här och där, men ... Se till att förstå hur hygien fungerar innan du försöker vara smartare än det! Det är inte en särskilt komplex algoritm så det borde inte ta för lång tid, men rusa inte! Använd inte den kraften förrän du förstår alla konsekvenserna av den ... (@fcard)

Exempel: until makro

(@ Ismael-VC)

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

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

(@fcard) |> är emellertid kontroversiell. Jag är förvånad över att en folkmassa inte har kommit att argumentera ännu. (kanske alla är bara trötta på det). Det finns en rekommendation att ha de flesta om inte alla makroerna bara vara ett samtal till en funktion, så:

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

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

... är ett säkrare alternativ.

## @ fcards enkla makroutmaning

Uppgift: Byt operander, så att swaps(1/2) ger 2.00 dvs. 2/1

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

Fler makroutmaningar från @fcard här


Interpolering och assert makro

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

F: Varför de sista $ ? S: Det interpoleras, dvs tvingar Julia att eval den string(ex) när exekvering passerar påkallandet av detta makro. dvs. om du bara kör den koden kommer den inte att tvinga någon utvärdering. Men i det ögonblick du assert(foo) kommer Julia att åberopa detta makro och ersätta sitt 'AST-token / Expr' med vad det än returnerar, och $ kommer att starta.

En rolig hack för att använda {} för block

(@fcard) Jag tror inte att det är något tekniskt hålla {} från att användas som block, i själva verket kan man även vits på resterande {} syntax för att få det att fungera:

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

* (sannolikt att det fortfarande inte fungerar om / när {} syntaxen återanvändas)


Så först ser Julia makrotoken, så kommer den att läsa / analysera symboler tills matchningen end , och skapa vad? En Expr med .head=:macro eller något? Lagrar den "a+1" som en sträng eller delar den upp i :+(:a, 1) ? Hur ser du på?

?

(@fcard) I det här fallet på grund av lexikalisk omfattning är a odefinierat i @M s räckvidd så det använder den globala variabeln ... Jag glömde faktiskt att undkomma flipplins uttryck i mitt dumma exempel, men "fungerar bara inom samma modul " del av det gäller fortfarande.

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

julia> a = 1
1

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

Anledningen är att om makroen används i någon annan modul än den som den definierades i, behandlas alla variabler som inte definieras i koden som ska utvidgas som globalt i makromodulen.

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

AVANCERAD

### @ 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

Scotts makro:

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

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

##Return

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

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

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

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

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

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

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

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

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

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

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

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

skräp / obearbetad ...

visa / dumpa ett makro

(@ pi-) Antag att jag bara gör macro m(); a+1; end i ett nytt REPL. Utan a definierad. Hur kan jag "visa" det? är det något sätt att "dumpa" ett makro? Utan att verkligen utföra det

(@fcard) Alla koder i makron läggs faktiskt in i funktioner, så att du bara kan se deras sänkta eller typindelade kod.

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!

Andra sätt att få en makrofunktion:

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)

^ ser ut som om jag kan använda code_typed istället

Hur förstår eval(Symbol("@M")) ?

(@fcard) För närvarande har varje makro en funktion associerad med den. Om du har ett makro som heter M kallas @M . Generellt kan du få en funktionsvärde med t.ex. eval(:print) men med en makrofunktion måste du göra Symbol("@M") , eftersom bara :@M blir en Expr(:macrocall, Symbol("@M")) och utvärdering som orsakar en makro-expansion.

Varför visar inte code_typed typparam?

(@pi-)

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

^ här ser jag en ::Any parametrar, men det verkar inte vara kopplat till token x .

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

^ på liknande sätt här; det finns inget att ansluta io till ::IO Så detta kan verkligen inte vara en fullständig dumpning av AST-representationen för den specifika print ...?

(@fcard) print(::IO, ::Char) berättar bara vilken metod det är, det är inte en del av AST. Det är inte ens närvarande i master längre:

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

(@ pi-) Jag förstår inte vad du menar med det. Det verkar dumpa AST för den metodens kropp, nej? Jag trodde code_typed ger AST för en funktion. Men det verkar sakna det första steget, dvs att ställa in symboler för params.

(@fcard) code_typed är enbart avsedd att visa kroppens AST, men för tillfället ger det den kompletta AST för metoden, i form av en LambdaInfo (0.5) eller CodeInfo (0.6), men mycket av informationen utelämnas när det skrivs ut till svaret. Du måste inspektera LambdaInfo fältet efter fält för att få alla detaljer. dump kommer att översvämma ditt svar, så du kan prova:

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])

Som ger alla värden för de namngivna fälten i metodens AST:

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)

Se lil ' def = print(io::IO, c::Char) ? Varsågod! (även slotnames = [..., :io, :c] del) Också ja, skillnaden i output beror på att jag visade resultaten på master.

???

(@ Ismael-VC) menar du så här? Generisk leverans med symboler

Du kan göra det på detta sätt:

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)

Detta är för Euler-algoritmen! Jag undrar vad tycker @fcard om generiska symbolsändningar! --- ^: ängel:

Modul Gotcha

@def m begin
  a+2
end

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

Mer exakt fungerar bara inom toppnivån för den modul som makroen definierades i.

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 att detta händer, men att alltid använda det strider mot språkdesignen. Ett bra försvar för detta är att hindra en från att använda och införa namn i makron, vilket gör dem svåra att spåra till en mänsklig läsare.

Python `dict` / JSON gillar syntax för` Dict`-bokstäver.

Introduktion

Julia använder följande syntax för ordböcker:

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

Medan Python och JSON ser ut så här:

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

För illustrativa syften kan vi också använda denna syntax i Julia och lägga till ny semantik till den ( Dict syntax är det idiomatiska sättet i Julia, vilket rekommenderas).

Låt oss först se vilken typ av uttryck det är:

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

Det betyder att vi måste ta detta :cell1d uttryck och antingen omvandla det eller returnera ett nytt uttryck som ska se ut så här:

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

Makro definition

Följande makro tillåter, även om det är enkelt, att demonstrera sådan kodgenerering och transformation:

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

Låt oss kolla den resulterande makroutvidgningen:

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

Användande

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         

Det sista exemplet motsvarar exakt:

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

felaktig användning

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ₙ}`

Lägg märke till att Julia har andra användningsområden för kolon : som sådan måste du radla in bokstavliga uttryck med parentes eller använda range , till exempel.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow