Szukaj…


Składnia

  • nazwa makra (ex) ... koniec
  • cytat ... koniec
  • : (...)
  • $ x
  • Meta.quot (x)
  • QuoteNode (x)
  • esc (x)

Uwagi

Funkcje metaprogramowania Julii są mocno zainspirowane funkcjami języków podobnych do Lisp i będą wydawać się znajome osobom z pewnym doświadczeniem Lisp. Metaprogramowanie jest bardzo potężne. Prawidłowe użycie może prowadzić do bardziej zwięzłego i czytelnego kodu.

quote ... end to składnia quasi-cytat. Zamiast ocenianych wyrażeń, są one po prostu analizowane. Wartością wyrażenia quote ... end jest wynikowe drzewo składni abstrakcyjnej (AST).

Składnia :(...) jest podobna do składni quote ... end ale jest bardziej lekka. Ta składnia jest bardziej zwięzła niż quote ... end .

W quasi-cytacie operator $ jest wyjątkowy i interpoluje swój argument w AST. Argument ma być wyrażeniem składanym bezpośrednio w AST.

Funkcja Meta.quot(x) cytuje swój argument. Jest to często przydatne w połączeniu z użyciem $ do interpolacji, ponieważ pozwala dosłownie wstawiać wyrażenia i symbole w AST.

Reimplementing makra @show

W Julii makro @show jest często przydatne do celów debugowania. Wyświetla zarówno wyrażenie do oceny, jak i jego wynik, zwracając w końcu wartość wyniku:

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

Stworzenie własnej wersji @show jest proste:

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

Aby użyć nowej wersji, wystarczy użyć makra @myshow :

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

julia> x
2

Aż do pętli

Wszyscy jesteśmy przyzwyczajeni do składni while , która wykonuje swoje ciało, gdy warunek jest oceniany jako true . Co jeśli chcemy wdrożyć until pętli, który wykonuje pętlę, aż stan ocenia się true ?

W Julii możemy to zrobić, tworząc makro @until , które przestaje wykonywać swoje ciało po spełnieniu warunku:

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

Użyliśmy tutaj składni funkcji łączenia łańcuchów |> , która jest równoważna z wywołaniem funkcji esc na całym bloku quote . Funkcja esc zapobiega stosowaniu się higieny makr do zawartości makra; bez niego zmienne o zasięgu w makrze zostaną przemianowane, aby zapobiec kolizji ze zmiennymi zewnętrznymi. Więcej informacji znajduje się w dokumentacji Julii dotyczącej higieny makro .

W tej pętli możesz użyć więcej niż jednego wyrażenia, po prostu umieszczając wszystko w bloku begin ... end :

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 i Expr (: quote)

Istnieją trzy sposoby zacytowania czegoś za pomocą funkcji Julii:

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

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

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

Co oznacza „cytowanie” i do czego służy? Cytowanie pozwala nam chronić wyrażenia przed interpretacją przez Julię jako formy specjalne. Typowym przypadkiem użycia jest generowanie wyrażeń, które powinny zawierać elementy, które mają postać symboli. (Na przykład to makro musi zwrócić wyrażenie, które zwraca wartość do symbolu.) Nie działa po prostu zwrócenie symbolu:

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

julia> @mysym
ERROR: UndefVarError: x not defined

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

Co tu się dzieje? @mysym rozwija się do :x , który jako wyrażenie zostaje zinterpretowany jako zmienna x . Ale nic nie zostało jeszcze przypisane do x , więc otrzymujemy błąd x not defined .

Aby obejść ten problem, musimy zacytować wynik naszego makra:

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

julia> @mysym2
:x

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

W tym przypadku Meta.quot funkcji Meta.quot aby przekształcić nasz symbol w cytowany symbol, co jest wynikiem, którego chcemy.

Jaka jest różnica między Meta.quot a QuoteNode i z których powinienem korzystać? W prawie wszystkich przypadkach różnica tak naprawdę nie ma znaczenia. Być może czasem trochę bezpieczniej jest używać QuoteNode zamiast Meta.quot . Jednak odkrycie różnicy jest pouczające o tym, jak działają wyrażenia i makra Julii.

Meta.quot różnicy między Meta.quot a QuoteNode

Oto ogólna zasada:

  • Jeśli potrzebujesz lub chcesz wesprzeć interpolację, użyj Meta.quot ;
  • Jeśli nie możesz lub nie chcesz zezwalać na interpolację, użyj QuoteNode .

Krótko mówiąc, różnica polega na tym, że Meta.quot umożliwia interpolację w obrębie cytowanej rzeczy, podczas gdy QuoteNode chroni swój argument przed każdą interpolacją. Aby zrozumieć interpolację, ważne jest, aby wspomnieć o wyrażeniu $ . W Julii istnieje rodzaj wyrażenia zwanego wyrażeniem $ . Te wyrażenia pozwalają na ucieczkę. Rozważmy na przykład następujące wyrażenie:

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

Po ocenie, to wyrażenie oceni 1 i przypisze je do x , a następnie skonstruuje wyrażenie w formie _ + _ gdzie _ zostanie zastąpione wartością x . Wynikiem tego powinno być wyrażenie 1 + 1 (które nie zostało jeszcze ocenione, a więc różni się od wartości 2 ). Rzeczywiście dzieje się tak:

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

Powiedzmy teraz, że piszemy makro, aby zbudować tego rodzaju wyrażenia. Nasze makro przyjmie argument, który zastąpi 1 w ex powyżej. Ten argument może oczywiście być dowolnym wyrażeniem. Oto coś, czego nie do końca chcemy:

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

Drugi przypadek jest niepoprawny, ponieważ nie powinniśmy oceniać wartości 1 + 1 . Meta.quot to, cytując argument w 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

Makro higiena nie ma zastosowania do treści cytatu, więc ucieczka nie jest w tym przypadku konieczna (aw rzeczywistości nielegalna).

Jak wspomniano wcześniej, Meta.quot umożliwia interpolację. Wypróbujmy to:

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

Z pierwszego przykładu widzimy, że interpolacja pozwala nam na wprowadzenie sin(1) , zamiast wyrażenia jako sin(1) dosłowny sin(1) . Drugi przykład pokazuje, że interpolacja odbywa się w zakresie wywołania makra, a nie we własnym zakresie makra. Wynika to z faktu, że nasze makro faktycznie nie oceniało żadnego kodu; wszystko, co robi, to generowanie kodu. Ocena kodu (który trafia do wyrażenia) jest wykonywana, gdy wyrażenie generowane przez makro jest faktycznie uruchamiane.

Co jeśli zamiast tego QuoteNode ? Jak można się domyślić, ponieważ QuoteNode uniemożliwia interpolację, oznacza to, że nie będzie działać.

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

W tym przykładzie możemy zgodzić się, że Meta.quot zapewnia większą elastyczność, ponieważ umożliwia interpolację. Dlaczego więc moglibyśmy kiedykolwiek rozważyć użycie QuoteNode ? W niektórych przypadkach możemy nie chcieć interpolacji i chcemy dosłownie wyrażenia $ . Kiedy byłoby to pożądane? Rozważmy uogólnienie @makeex którym możemy przekazać dodatkowe argumenty określające, co dzieje się po lewej i prawej stronie znaku + :

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)

Ograniczeniem naszej implementacji @makeex4 jest to, że nie możemy używać wyrażeń jako lewej i prawej strony wyrażenia bezpośrednio, ponieważ są one interpolowane. Innymi słowy, wyrażenia mogą zostać ocenione pod kątem interpolacji, ale możemy chcieć je zachować. (Ponieważ istnieje wiele poziomów cytowania i oceny, wyjaśnijmy: nasze makro generuje kod, który konstruuje wyrażenie, które po ocenie tworzy kolejne wyrażenie . Uff!)

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)

Powinniśmy pozwolić użytkownikowi określić, kiedy ma nastąpić interpolacja, a kiedy nie. Teoretycznie jest to łatwa poprawka: możemy po prostu usunąć jeden ze znaków $ w naszej aplikacji i pozwolić użytkownikowi wnieść swój własny. Oznacza to, że interpolujemy cytowaną wersję wyrażenia wprowadzonego przez użytkownika (który już cytowaliśmy i interpolowaliśmy raz). Prowadzi to do następującego kodu, który na początku może być nieco mylący ze względu na wiele zagnieżdżonych poziomów cytowania i cofania cudzysłowu. Spróbuj przeczytać i zrozumieć, do czego służy każda ucieczka.

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

Wszystko zaczęło się dobrze, ale coś poszło nie tak. Wygenerowany kod makra próbuje interpolować kopię y w zakresie wywołania makra; ale nie ma kopii y w zakresie wywołania makra. Nasz błąd pozwala na interpolację z drugim i trzecim argumentem w makrze. Aby naprawić ten błąd, musimy użyć 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)

Korzystając z QuoteNode , chroniliśmy nasze argumenty przed interpolacją. Ponieważ QuoteNode ma jedynie wpływ na dodatkowe zabezpieczenia, korzystanie z QuoteNode nigdy nie jest szkodliwe, chyba że chcesz interpolacji. Jednak zrozumienie różnicy pozwala zrozumieć, gdzie i dlaczego Meta.quot może być lepszym wyborem.

To długie ćwiczenie jest przykładem, który jest po prostu zbyt skomplikowany, aby mógł pojawić się w jakimkolwiek rozsądnym zastosowaniu. Dlatego uzasadniliśmy następującą zasadę, wspomnianą wcześniej:

  • Jeśli potrzebujesz lub chcesz wesprzeć interpolację, użyj Meta.quot ;
  • Jeśli nie możesz lub nie chcesz zezwalać na interpolację, użyj QuoteNode .

Co z wyrażeniem (: cytat)?

Expr(:quote, x) jest równoważne Meta.quot(x) . Ten ostatni jest jednak bardziej idiomatyczny i jest preferowany. Kodu, który w dużym stopniu wykorzystuje na meta, A using Base.Meta linia jest często używany, co pozwala Meta.quot być nazywane po prostu quot .

Przewodnik

π bity i boby Metaprogramowania

Cele:

  • Naucz przez minimalne ukierunkowane funkcjonalne / przydatne / @swap przykłady (np. @swap lub @assert ), które wprowadzają pojęcia w odpowiednich kontekstach

  • Wolę, aby kod ilustrował / demonstrował pojęcia zamiast akapitów objaśnień

  • Unikaj linkowania „wymaganego czytania” do innych stron - przerywa to narrację

  • Prezentuj rzeczy w rozsądnej kolejności, która ułatwi naukę

Zasoby:

julialang.org
wikibook (@Cormullion)
5 warstw (Leah Hanson)
Cytowanie w SO-Doc (@TotalVerb)
SO-Doc - Symbole, które nie są prawnymi identyfikatorami (@TotalVerb)
SO: What is a Symbol in Julia (@StefanKarpinski)
Dyskurs wątku (@ pi-) Metaprogramowanie

Większość materiałów pochodzi z kanału dyskursu, większość pochodzi z fcard ... proszę, daj mi znać, jeśli zapomniałem atrybucji.

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

Przekazywanie flag do funkcji:

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

Przykład skrótu:

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

(Zaawansowany) (@fcard) :foo aka :(foo) zwraca symbol, jeśli foo jest prawidłowym identyfikatorem, w przeciwnym razie wyrażenie.

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

Zasadniczo możesz traktować symbole jako lekkie ciągi znaków. Nie po to są, ale możesz to zrobić, więc dlaczego nie. Sama Baza Julii to robi, print_with_color(:red, "abc") drukuje abc w kolorze czerwonym.

Expr (AST)

(Prawie) wszystko w Julii jest wyrażeniem, tzn. Expr wyrażenia, które będzie zawierać 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

Expr zagnieżdżone:

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 za pomocą 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

... więc quote jest taka sama, ale zapewnia dodatkowe informacje debugowania.

(*) WSKAZÓWKA : Użyj let aby trzymać x w bloku

quote - quote

Expr(:quote, x) służy do reprezentowania cytatów w cudzysłowach.

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

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

QuoteNode(x) jest podobny do Expr(:quote, x) ale zapobiega interpolacji.

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

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

( Ujednoznacznij różne mechanizmy cytowania w metaprogramowaniu Julii

Czy $ i : (…) są w jakiś sposób odwrócone względem siebie?

:(foo) oznacza „nie patrz na wartość, spójrz na wyrażenie” $foo oznacza „zmień wyrażenie na jego wartość”

:($(foo)) == foo . $(:(foo)) jest błędem. $(...) nie jest operacją i nic nie robi samo z siebie, to „interpoluj to!” znak, że używa składni cytowania. tzn. istnieje tylko w cytacie.

Czy $ foo tym samym co eval( foo ) ?

Nie! $foo jest wymieniane na wartość eval(foo) czasie kompilacji eval(foo) oznacza to w czasie wykonywania

eval nastąpi w globalnej interpolacji zakresu jest lokalna

eval(:<expr>) powinien zwracać to samo co tylko <expr> (zakładając, że <expr> jest poprawnym wyrażeniem w bieżącej przestrzeni globalnej)

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

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

macro s

Gotowy? :)

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

Stwórzmy własne makro @show :

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

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

expand aby obniżyć Expr

5 warstw (Leah Hanson) <- wyjaśnia, w jaki sposób Julia bierze kod źródłowy jako ciąg znaków, tokenizuje go w Expr (AST), rozwija wszystkie makra (wciąż AST), obniża (obniża AST), a następnie przekształca w LLVM (i nie tylko - w tej chwili nie musimy się martwić o to, co leży dalej!)

P: code_lowered działa na funkcje. Czy można obniżyć Expr ? Odp .: Tak!

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

Jeśli chcesz tylko rozwinąć makra, możesz użyć 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

... która zwraca nie obniżony poziom AST, ale z rozszerzonymi wszystkimi makrami.

esc()

esc(x) zwraca wyrażenie, które mówi „nie stosuj higieny”, jest to to samo, co Expr(:escape, x) . Higiena jest tym, co sprawia, że makro jest samowystarczalne, a esc rzeczami, jeśli chcesz, aby „wyciekły”. na przykład

Przykład: swap makro w celu zilustrowania 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

$ pozwala nam „uciec z” quote . Dlaczego więc nie po prostu $p i $q ? to znaczy

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

Ponieważ najpierw macro zakresu macro dla p , i znalazłoby lokalne p tj. Parametr p (tak, jeśli następnie uzyskasz dostęp do p bez esc -ing, makro traktuje parametr p jako zmienną lokalną).

Więc $p = ... to tylko przypisanie do lokalnego p . nie wpływa na żadną zmienną przekazaną w kontekście wywołującym.

Ok, a co powiesz na:

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

Zatem esc(p) „przecieka” p do kontekstu wywołującego. „Rzecz, która została przekazana do makra, które otrzymujemy jako 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                                      

Jak widać tmp Pobiera Zachowanie higieny #10#tmp , a x i y nie. Julia tworzy unikalny identyfikator tmp , coś, co można zrobić ręcznie za pomocą gensym , np .:

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

Ale: Jest 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

Inną rzeczą, jaką robi higiena makr Julii, jest to, że jeśli makro pochodzi z innego modułu, powoduje to, że wszystkie zmienne (które nie zostały przypisane do wyrażenia zwracającego makro, w tym przypadku tmp ) są globalne bieżącego modułu, więc $p staje się Swap.$p , podobnie $q -> Swap.$q .

Zasadniczo, jeśli potrzebujesz zmiennej, która jest poza zakresem makra, powinieneś ją usunąć, więc powinieneś esc(p) i esc(q) niezależnie od tego, czy znajdują się w LHS, czy w RHS wyrażenia, czy nawet same.

ludzie już gensym wspominali o gensym sa, a wkrótce zostaniesz uwiedziony przez ciemną stronę gensym ucieczki przed całym wyrażeniem za pomocą kilku gensym tu i tam, ale ... Upewnij się, że rozumiesz, jak higiena działa, zanim spróbujesz być mądrzejszy niż to! Nie jest to szczególnie złożony algorytm, więc nie powinno to zająć zbyt długo, ale nie spiesz się! Nie używaj tej mocy, dopóki nie zrozumiesz wszystkich jej konsekwencji ... (@fcard)

Przykład: until makra

(@ 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) |> jest jednak kontrowersyjny. Dziwi mnie, że tłum jeszcze się nie kłócił. (może wszyscy są po prostu zmęczeni). Zaleca się, aby większość, jeśli nie całe makro, stanowiło tylko wywołanie funkcji, więc:

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

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

... jest bezpieczniejszą alternatywą.

Proste wyzwanie makro ## @ fcard

Zadanie: Zamień operandy, więc swaps(1/2) daje 2.00 czyli 2/1

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

Więcej wyzwań makro z @fcard tutaj


Interpolacja i 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

P: Dlaczego ostatnie $ ? Odp .: interpoluje, tzn. Zmusza Julię do eval tego string(ex) gdy wykonanie przechodzi przez wywołanie tego makra. tzn. jeśli uruchomisz ten kod, nie wymusi on żadnej oceny. Ale chwila ty assert(foo) Julia wywoła to makro zastępując jego „AST żeton / Expr” z tym, co zwraca, a $ ruszy do akcji.

Zabawny hack do używania {} do bloków

(@fcard) Nie sądzę, żeby coś technicznego powstrzymywało {} od użycia jako bloków, w rzeczywistości można nawet wykasować resztkową składnię {} aby to działało:

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

* (raczej nie będzie działać, jeśli / kiedy zmieniona zostanie składnia {})


Więc najpierw Julia widzi token makra, więc będzie czytał / analizował tokeny aż do pasującego end i co stworzył? Expr z .head=:macro czy coś? Czy przechowuje "a+1" jako ciąg, czy też dzieli go na :+(:a, 1) ? Jak wyświetlić?

?

(@fcard) W tym przypadku ze względu na zakres leksykalny, a nie jest zdefiniowane w zakresie @M więc używa zmiennej globalnej ... Właściwie zapomniałem uciec od wyrażenia flipplin w moim głupim przykładzie, ale „działa tylko w ten sam moduł ”, jego część nadal obowiązuje.

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

julia> a = 1
1

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

Powodem jest to, że jeśli makro jest używane w dowolnym module innym niż ten, w którym zostało zdefiniowane, wszelkie zmienne niezdefiniowane w kodzie, który ma zostać rozszerzony, są traktowane jako globale modułu makra.

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

ZAAWANSOWANE

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

Makro Scotta:

"""
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

śmieci / nieprzetworzone ...

wyświetl / zrzuć makro

(@ pi-) Załóżmy, że po prostu wykonuję macro m(); a+1; end na świeżym REPL. Bez a zdefiniowany. Jak mogę to „wyświetlić”? na przykład, czy jest jakiś sposób na „zrzucenie” makra? Bez faktycznego wykonania

(@fcard) Cały kod w makrach jest faktycznie wprowadzany do funkcji, więc można wyświetlić tylko jego kod obniżony lub kod wnioskowania na podstawie typu.

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!

Inne sposoby uzyskania funkcji makra:

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)

^ wygląda na to, że zamiast tego mogę użyć code_typed

Jak rozumieć eval(Symbol("@M")) ?

(@fcard) Obecnie każde makro ma przypisaną funkcję. Jeśli masz makro o nazwie M , wówczas funkcja makra nazywa się @M . Zasadniczo wartość funkcji można uzyskać za pomocą np. eval(:print) ale za pomocą funkcji makra musisz wykonać Symbol("@M") , ponieważ tylko :@M staje się Expr(:macrocall, Symbol("@M")) i ocena, która powoduje makroekspansję.

Dlaczego parametry_wyświetlane code_typed nie wyświetlają?

(@Liczba Pi-)

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

^ tutaj widzę jeden ::Any parametr, ale wydaje się, że nie jest powiązany z tokenem x .

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

^ podobnie tutaj; nie ma nic do połączenia io z ::IO pewno nie może to być kompletny zrzut reprezentacji AST tej konkretnej metody print …?

(@fcard) print(::IO, ::Char) mówi tylko, jaka to metoda, nie jest częścią AST. Nie ma go nawet w Master:

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

(@ pi-) Nie rozumiem, co masz na myśli. Wydaje się, że odrzuca AST dla całej tej metody, nie? Myślałem, że code_typed daje AST dla funkcji. Ale wydaje się, że brakuje pierwszego kroku, tj. Ustawienia tokenów dla parametrów.

(@fcard) code_typed ma pokazywać tylko AST ciała, ale na razie podaje pełny AST metody, w postaci LambdaInfo (0,5) lub CodeInfo (0.6), ale wiele informacji jest pomijanych po wydrukowaniu na repl. Będziesz musiał sprawdzić pole LambdaInfo według pola, aby uzyskać wszystkie szczegóły. dump będzie zalewał twoją replikę, więc możesz spróbować:

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

Co daje wszystkie wartości nazwanych pól metody 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)

Widzisz lil ' def = print(io::IO, c::Char) ? Proszę bardzo! (także część slotnames = [..., :io, :c] ) Także tak, różnica w wynikach wynika z tego, że pokazywałem wyniki na masterie.

???

(@ Ismael-VC) masz na myśli tak? Ogólna wysyłka z symbolami

Możesz to zrobić w ten sposób:

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)

To dotyczy algorytmu Eulera! Zastanawiam się, co @fcard myśli o wysyłce symboli ogólnych! --- ^: anioł:

Moduł Gotcha

@def m begin
  a+2
end

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

Dokładniej, działa tylko na najwyższym poziomie modułu, w którym makro zostało zdefiniowane.

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 powstrzymuje to przed działaniem, ale domyślnie zawsze używa się go wbrew projektowi języka. Dobrą obroną przed tym jest powstrzymanie się od używania i wprowadzania nazw w makrach, co sprawia, że trudno jest je namierzyć dla czytelnika.

Python `dict` / JSON jak składnia dla literałów` Dict`.

Wprowadzenie

Julia używa następującej składni dla słowników:

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

Podczas gdy Python i JSON wygląda następująco:

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

Dla celów ilustracyjnych moglibyśmy również użyć tej składni w Julii i dodać do niej nową semantykę (składnia Dict jest idiomatycznym sposobem w Julii, co jest zalecane).

Najpierw zobaczmy, jaki rodzaj wypowiedzi jest:

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

Oznacza to, że musimy wziąć to :cell1d wyrażenie :cell1d i przekształcić je lub zwrócić nowe wyrażenie, które powinno wyglądać tak:

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

Definicja makra

Poniższe makro, choć proste, pozwala zademonstrować generowanie i transformację kodu:

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

Sprawdźmy wynikowe rozwinięcie makra:

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

Stosowanie

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         

Ostatni przykład jest dokładnie równoważny z:

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

Zauważ, że Julia ma inne zastosowania dla dwukropka : jako takie będziesz musiał zawijać wyrażenia dosłowne zakresu nawiasami lub na przykład użyć funkcji range .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow