Julia Language
Metaprogramowanie
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 kontekstachWolę, 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
.