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 /
@swapprzykłady (np.@swaplub@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 .