Julia Language
メタプログラミング
サーチ…
構文
- マクロ名(ex)... end
- 引用...終わり
- :(...)
- $ x
- Meta.quot(x)
- QuoteNode(x)
- esc(x)
備考
Juliaのメタプログラミング機能は、Lispのような言語のものに大きく影響されており、Lispの背景に慣れているように見えます。メタプログラミングは非常に強力です。正しく使用すると、より簡潔で読みやすいコードになる可能性があります。
quote ... end
はquasiquote構文です。評価中の式の代わりに、単純に解析されます。 quote ... end
式の値は、結果の抽象構文木(AST)です。
:(...)
構文はquote ... end
構文と似ていますが、より軽量です。この構文はquote ... end
よりも簡潔quote ... end
。
quasiquoteの中では、 $
演算子は特別で、引数をASTに補間します。引数は、ASTに直接スプライスされる式であることが期待されます。
Meta.quot(x)
関数は引数を引用します。これは、式と記号を文字どおりASTにつなげることができるので、補間に$
を使うことと組み合わせて便利です。
@showマクロを再実装する
Juliaでは、 @show
マクロはデバッグの目的でしばしば役に立ちます。評価される式とその結果の両方を表示し、最後に結果の値を返します。
julia> @show 1 + 1
1 + 1 = 2
2
@show
独自のバージョンを作成するのは簡単@show
:
julia> macro myshow(expression)
quote
value = $expression
println($(Meta.quot(expression)), " = ", value)
value
end
end
新しいバージョンを使用するには、単に@myshow
マクロを使用し@myshow
:
julia> x = @myshow 1 + 1 1 + 1 = 2 2 julia> x 2
ループまで
私たちはすべてwhile
構文に慣れており、条件がtrue
と評価されている間に本体を実行しtrue
。 until
ループを実装したい場合、条件がtrue
評価されるuntil
ループを実行しtrue
か?
Juliaでは、条件が満たされたときに本体の実行を停止する@until
マクロを作成することでこれを行うことができます:
macro until(condition, expression) quote while !($condition) $expression end end |> esc end
ここでは、関数連鎖構文|>
を使用しました。これは、 quote
ブロック全体でesc
関数を呼び出すのと同じです。 esc
関数は、マクロの衛生状態がマクロの内容に適用されないようにします。それがなければ、マクロでスコープされた変数は、外部変数との衝突を防ぐために名前が変更されます。詳細は、 マクロ衛生に関するジュリアの文書を参照してください。
このループでは、 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、およびExpr(:quote)
Julia関数を使って何かを引用するには3つの方法があります:
julia> QuoteNode(:x)
:(:x)
julia> Meta.quot(:x)
:(:x)
julia> Expr(:quote, :x)
:(:x)
「引用」とは何を意味し、それは何のために良いのですか?クォートでは、ジュリアによる特別な書式としての表現を保護することができます。一般的な使用例は、シンボルに評価されるものを含むべき式を生成する場合です。 (たとえば、 このマクロは、シンボルに評価される式を返す必要があります)。シンボルを返すだけでは機能しません:
julia> macro mysym(); :x; end
@mysym (macro with 1 method)
julia> @mysym
ERROR: UndefVarError: x not defined
julia> macroexpand(:(@mysym))
:x
何が起きてる? @mysym
は:x
に展開され:x
は式が変数x
として解釈されるようになります。しかし、まだx
は何も割り当てられていないので、 x not defined
エラーになります。
これを回避するには、マクロの結果を引用する必要があります。
julia> macro mysym2(); Meta.quot(:x); end
@mysym2 (macro with 1 method)
julia> @mysym2
:x
julia> macroexpand(:(@mysym2))
:(:x)
ここでは、 Meta.quot
関数を使用してシンボルを引用符で囲んだシンボルにしています。
Meta.quot
とQuoteNode
違いは何ですか?どのようなものを使用しますか?ほとんどすべての場合、その違いは関係ありません。 QuoteNode
代わりにMeta.quot
を使用するのは、たぶん少し安全Meta.quot
。しかし、その違いを調べることは、ジュリアの表現とマクロがどのように機能するかに役立ちます。
Meta.quot
とQuoteNode
の違いについて説明しました
経験則があります:
- あなたが補間を必要としている場合、または補間をサポートしたい場合は、
Meta.quot
; - 補間を許可できない、またはしたくない場合は、
QuoteNode
使用しQuoteNode
。
要するに、違いがあるということMeta.quot
一方で、引用されたものの中に補間することができますQuoteNode
任意の補間からその引数を保護します。補間を理解するためには、 $
式について言及することが重要です。 Juliaには、 $
式という一種の表現があります。これらの表現はエスケープすることができます。たとえば、次の式を考えてみましょう。
julia> ex = :( x = 1; :($x + $x) )
quote
x = 1
$(Expr(:quote, :($(Expr(:$, :x)) + $(Expr(:$, :x)))))
end
評価されると、この式は1
を評価してx
に代入し、 _ + _
式を構成します。ここで、 _
はx
の値に置き換えられます。したがって、結果は式 1 + 1
(これはまだ評価されておらず、 値 2
異なる)でなければなりません。確かに、これは事実です:
julia> eval(ex)
:(1 + 1)
私たちがこれらの種類の式を構築するためのマクロを書いていることを考えてみましょう。私たちのマクロは上記のex
1
を置き換える引数を取るでしょう。もちろん、この引数は任意の式にすることができます。ここには私たちが望むものではないものがあります:
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
私たちは1 + 1
未評価にしておくべきだから、2番目のケースは間違っています。 Meta.quot
引数を引用して修正し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
マクロ衛生は見積もりの内容には適用されないので、この場合エスケープする必要はありません(実際には合法ではありません)。
前述のように、 Meta.quot
は補間を許可します。だからそれを試してみましょう:
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
最初の例から、式をリテラルsin(1)
するのではなく、補間によってsin(1)
をインライン化できることがわかります。 2番目の例は、この補間がマクロの呼び出しスコープで行われ、マクロのスコープでは行われないことを示しています。これは、マクロが実際にコードを評価していないためです。コードを生成するだけです。マクロが生成する式が実際に実行されたときに、コードの評価が行われます。
代わりにQuoteNode
を使用した場合はどうなりますか?あなたが推測するように、 QuoteNode
は補間が全く起こらないようにするので、動作しません。
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
この例では、補間が可能であるため、 Meta.quot
はより大きな柔軟性を提供することに同意するかもしれません。 QuoteNode
使用を検討する理由はQuoteNode
ですか?場合によっては、補間を実際には望んでおらず、実際にリテラル$
式が必要な場合もあります。いつそれが望ましいでしょうか? @makeex
一般化について考えてみましょう。ここで+
記号の左右に何が来るのかを決定する追加の引数を渡すことができます:
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)
私たちの@makeex4
実装の限界は、表現を式の左辺と右辺のどちらかとして直接使用することができないということです。言い換えれば、式は補間のために評価されるかもしれないが、それらを保存しておきたいかもしれない。 (ここでは引用と評価に多くのレベルがあるので、私たちのマクロは、評価されると別の式を生成する式を構成するコードを生成します 。
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)
私たちは、補間をいつ実行するのか、いつ行うべきでないのかをユーザーが指定できるようにすべきです。理論的には、これは簡単な修正です。アプリケーションの$
記号の1つを削除し、ユーザーが自分のものに貢献できるようにするだけです。これは、ユーザーが入力した式の引用バージョンを補間することを意味します(既に引用して補間しています)。これは、次のコードにつながります。これは、引用符と引用符の複数のネストされたレベルのため、最初は少し混乱する可能性があります。それぞれのエスケープが何のためにあるのかを読んで理解してください。
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
物事はうまく始まったが、何かが間違っていた。マクロの生成コードは、マクロ呼び出しスコープ内のy
のコピーを補間しようとしています。マクロの呼び出しスコープにはy
のコピーはありません 。私たちのエラーは、マクロ内の2番目と3番目の引数を使った補間を可能にしています。このエラーを修正するには、 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)
QuoteNode
を使うQuoteNode
、引数を補間から保護しています。 QuoteNode
は追加の保護の効果しか持たないQuoteNode
、補間が必要な場合をQuoteNode
、 QuoteNode
を使用することは決して有害ではありません。しかし、その違いを理解することで、 Meta.quot
がより良い選択となる理由とその理由を理解することが可能になります。
この長いエクササイズは、合理的なアプリケーションではあまりにも複雑すぎて表示できない例です。したがって、前述の経験則を正当化しました。
- あなたが補間を必要としている場合、または補間をサポートしたい場合は、
Meta.quot
; - 補間を許可できない、またはしたくない場合は、
QuoteNode
使用しQuoteNode
。
Expr(:quote)はどうですか?
Expr(:quote, x)
はMeta.quot(x)
と同じです。しかしながら、後者はより慣用的であり、好ましい。メタプログラミングを大量に使用するコードでは、 using Base.Meta
行を使用することがよくあります。これにより、 Meta.quot
を単にquot
と呼ぶことができます。
ガイド
πのメタプログラミングビットとボブ
ゴール:
適切なコンテキストで概念を導入する、機能的/有用/非抽象的な目的の最小限の例(
@swap
や@assert
)を@assert
解説の段落ではなく概念を説明/説明できるようにする
「必要な読書」を他のページにリンクすることを避ける - それは物語を妨害する
賢明な順序で物事を提示することで、学習が最も簡単になります
リソース:
julialang.org
wikibook(@Cormullion)
5層(リーアハンソン)
SO-Doc引用(@ TotalVerb)
SO-Doc - 正当な識別子ではないシンボル(@TotalVerb)
SO:Julia(@StefanKarpinski)のシンボルとは何ですか?
談話スレッド(@ pi-) メタプログラミング
材料のほとんどは談話のチャンネルから来ています、そのほとんどはfcardから来ています...私が帰属を忘れてしまった場合は、私を助けてください。
シンボル
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
関数にフラグを渡す:
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
ハッシュキーの例:
number_names = Dict{Symbol, Int}() number_names[:one] = 1 number_names[:two] = 2 number_names[:six] = 6
(拡張) (@fcard) :foo
aka :(foo)
は、 foo
が有効な識別子であればシンボルを、そうでなければ式を生成します。
# 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 ...
基本的に、Symbolsを軽量文字列として扱うことができます。それは彼らのためのものではありませんが、あなたはそれを行うことができます、なぜそうではありませんか。 JuliaのBaseそれ自体はそれを行い、 print_with_color(:red, "abc")
は赤色のabcを出力します。
Expr (AST)
(ほぼ)Juliaのすべてがエクスプレッション、つまりASTを保持するExpr
インスタンスです。
# 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
:
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))
quote
を使用した複数行Expr
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
... quote
は機能的には同じですが、追加のデバッグ情報を提供します。
(*) ヒント : let
を使ってx
をブロック内に保持する
quote
quote
Expr(:quote, x)
はExpr(:quote, x)
引用符内の引用符を表すのに使用されます。
Expr(:quote, :(x + y)) == :(:(x + y))
Expr(:quote, Expr(:$, :x)) == :(:($x))
QuoteNode(x)
はExpr(:quote, x)
QuoteNode(x)
と似ていExpr(:quote, x)
補間はできません。
eval(Expr(:quote, Expr(:$, 1))) == 1
eval(QuoteNode(Expr(:$, 1))) == Expr(:$, 1)
( ジュリアのメタプログラミングにおける様々な引用機構を明確にする
$と:(...)どういうわけかお互いの逆転?
:(foo)
は "値を見ないで、式を見ます" $foo
は "式をその値に変更する"
:($(foo)) == foo
。 $(:(foo))
はエラーです。 $(...)
は演算ではなく、単独で何もしません。これは "これを補間する"引用構文が使用する記号。つまり、それは見積もり内にのみ存在します。
$
foo
はeval(
foo
)
と同じですか?
いいえ! $foo
がコンパイル時の値と交換されるeval(foo)
は、実行時にそれを行うことを意味します
グローバルスコープ内でeval
が発生する
eval(:<expr>)
ちょうど同じ返す必要があります<expr>
(仮定<expr>
現在の世界的な空間での有効な表現ですが)
eval(:(1 + 2)) == 1 + 2
eval(:(let x=1; x + 1 end)) == let x=1; x + 1 end
macro
準備? :)
# let's try to make this! julia> x = 5; @show x; x = 5
独自の@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
Expr
を下げるexpand
する
5層(リアハンソンが) < -ジュリアにそれをトークン化、文字列としてソースコードをとる方法について説明Expr
-tree(AST)、OUT(依然としてAST)すべてのマクロ、 低下 (低下AST)を展開し、次にLLVMに変換(そして、それを超えて - 私たちはそれ以上のものを心配する必要はありません!)
Q:関数にcode_lowered
作用します。 Expr
を下げることは可能ですか? A:うん!
# 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)
マクロを展開したいだけなら、 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
...非ASTを返しますが、すべてのマクロを展開します。
esc()
esc(x)
は "これに衛生状態を適用しない"というExprを返しますExpr(:escape, x)
と同じです。衛生はマクロを自己完結した状態に保つもので、あなたがそれらを "漏らす"ことを望むなら、あなたはesc
ます。例えば
例: esc()
を説明するためにマクロをswap
する
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
$
は、 quote
から「抜け出す」ことができquote
。なぜ単純に$p
と$q
ないのですか?すなわち、
# FAIL! tmp = $p $p = $q $q = tmp
それは、第1〜第見えるので、 macro
のスコープp
、およびそれがローカル見つけるだろうp
パラメータ、すなわち、 p
(あなたが後でアクセスする場合、はい、 p
せずにesc
-ing、マクロは考慮p
ローカル変数としてパラメータ)。
だから$p = ...
はローカルp
割り当てられているだけです。呼び出しコンテキストで渡された変数には影響しません。
それでは、
# Almost! tmp = $p # <-- you might think we don't $(esc(p)) = $q # need to esc() the RHS $(esc(q)) = tmp
したがって、 esc(p)
はp
を呼び出しコンテキストに「漏らしています」。 " 私たちが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
あなたが見ることができるように、 tmp
は衛生治療#10#tmp
tmp
得ますが、 x
とy
はそうではありません。 Juliaはtmp
一意の識別子を作成しています。これはgensym
で手動で行うことができます。
julia> gensym(:tmp) Symbol("##tmp#270")
しかし、つかの間があります:
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
マクロが別のモジュールからのものである場合もう一つのジュリアのマクロ衛生は、それが(のように、マクロの返却式の中に割り当てられていなかったすべての変数になり、あるんtmp
このケースでは)現在のモジュールのグローバルを、そう$p
なりSwap.$p
、同様に$q
- > Swap.$q
。
一般に、マクロのスコープの外側にある変数が必要な場合はescを実行する必要があるので、式のLHSまたはRHSにあるかどうかにかかわらずesc(p)
とesc(q)
実行する必要があります。
人々はすでにgensym
数回言及しています。そして、間もなくあなたは、ここでgensym
た少数のgensym
表現をエスケープするというデフォルトの暗い側面に誘惑されます。しかし、...しようとする前に衛生状態がどのように働くかを理解してくださいそれよりスマート!それは特に複雑なアルゴリズムではありませんので、時間がかかりすぎる必要はありませんが、急いではいけません!あなたはそれのすべての分派を理解するまで、その力を使用しないでください...(@fcard)
例:マクロuntil
(@ 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) |>
は議論の余地があります。私は暴動家がまだ論争していないことに驚いています。 (多分誰もがそれに疲れている)。マクロのすべてではないにしても、関数の呼び出しであることをお勧めします。
macro until(condition, block) esc(until(condition, block)) end function until(condition, block) quote while !$condition $block end end end
...より安全な選択肢です。
## @ fcardの簡単なマクロチャレンジ
タスク:オペランドをスワップするので、 swaps(1/2)
は2.00
つまり2/1
macro swaps(e) e.args[2:3] = e.args[3:-1:2] e end @swaps(1/2) 2.00
@fcardからのより多くのマクロ挑戦ここに
補間とassert
マクロ
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
Q:なぜ最後の$
? A:補間を行います。つまり、このマクロの呼び出しを実行が通過するときに、Juliaにそのstring(ex)
をeval
せます。つまり、そのコードを実行しただけでは、評価は強制されません。しかし、あなたがassert(foo)
をした瞬間、Juliaはこのマクロを呼び出して、それが返すものをAST token / Exprに置き換え、 $
が動作に移ります。
ブロックに{}を使用するための面白いハッキング
(@fcard)私は、技術的な{}
をブロックとして使用することは考えていませんが、実際には残余{}
構文を使って動作させることもできます:
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
*({}構文が再利用されている場合には、やはり動作しそうにない)
それは読み取り/一致するまで、トークンを解析するので、したがって、最初のジュリアは、マクロトークンを見end
、そして何を作りますか? .head=:macro
などを.head=:macro
たExpr
?それは文字列として"a+1"
を格納しているのか、またはそれを別々に分割していますか? :+(:a, 1)
?見る方法?
?
(@fcard)この場合、字句スコープのために、 @M
スコープでは定義されていないので、グローバル変数を使用します...私は実際には私の愚かな例ではなく、 "同じモジュール "の部分がそのまま適用されます。
julia> module M macro m() :(a+1) end end M julia> a = 1 1 julia> M.@m ERROR: UndefVarError: a not defined
その理由は、マクロが定義されているモジュール以外のモジュールで使用されている場合、拡張コード内で定義されていない変数はマクロのモジュールのグローバルとして扱われるからです。
julia> macroexpand(:(M.@m)) :(M.a + 1)
アドバンスト
### @イスマエル-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
スコットのマクロ:
"""
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
ジャンク/未処理...
マクロの表示 / ダンプ
(@ pi-) macro m(); a+1; end
だけを実行すると仮定しmacro m(); a+1; end
新鮮なREPLでmacro m(); a+1; end
ます。なしと定義されました。 a
どうすればそれを見ることができますか?マクロを「ダンプ」する方法はありますか?それを実際に実行することなく
(@fcard)マクロ内のすべてのコードは実際に関数に入れられます。
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!
マクロの機能を取得する他の方法:
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)
^代わりにcode_typed
を使用できるように見える
eval(Symbol("@M"))
を理解するには?
(@fcard)現在のところ、すべてのマクロには関連する関数があります。 M
というマクロがある場合、マクロの関数は@M
と呼ばれ@M
。一般的に、あなたは、例えば持つ関数の値を取得することができますeval(:print)
が、マクロの機能であなたは何をする必要があるSymbol("@M")
だけであるため、 :@M
となりExpr(:macrocall, Symbol("@M"))
、マクロ拡張の原因となるものを評価します。
なぜcode_typed
はcode_typed
表示しないのですか?
(π-)
julia> code_typed( x -> x^2 )[1] LambdaInfo for (::##5#6)(::Any) :(begin return x ^ 2 end)
^ここで私は1つを見ます::Any
param、それはトークンx
と接続されていないようです。
julia> code_typed( print )[1] LambdaInfo for print(::IO, ::Char) :(begin (Base.write)(io,c) return Base.nothing end::Void)
^こちらも同様です。 io
と::IO
を接続するものは何もありませんので、確かにこれは特定のprint
メソッドのAST表現を完全にダンプすることはできません。
(@fcard) print(::IO, ::Char)
は、どのメソッドであるかを伝えるだけで、ASTの一部ではありません。もうマスターには存在しません。
julia> code_typed(print)[1] CodeInfo(:(begin (Base.write)(io,c) return Base.nothing end))=>Void
(@ pi-)あなたはそれが何を意味するのか分かりません。その方法の体のためにASTをダンプしているようですが、それはありませんか?私はcode_typed
がASTに関数を与えると考えました。しかし、それは最初のステップを欠いているようです、すなわち、パラメータのためのトークンを設定する。
(@fcard) code_typed
は本体のASTのみを表示することを意図していますが、今のところLambdaInfo
(0.5)またはCodeInfo
(0.6)の形式でメソッドの完全なASTを提供しますが、多くの情報は省略されていますreplに印刷されたとき。すべての詳細を取得するには、 LambdaInfo
フィールドをフィールドLambdaInfo
に検査する必要があります。 dump
があなたのレプリカをあふれさせるので、試してみることができます:
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])
メソッドの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)
lil ' def = print(io::IO, c::Char)
参照してください。そこに行く! ( slotnames = [..., :io, :c]
part)また、出力の違いは、私がマスターで結果を表示していたためです。
???
(@ Ismael-VC)あなたはこのような意味ですか? Symbolsによる汎用ディスパッチ
あなたはこのようにすることができます:
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)
これはオイラーアルゴリズムのためのものです! @fcardが一般的なシンボルのディスパッチについて何を考えているのだろうか? --- ^:天使:
モジュールゴッチャ
@def m begin a+2 end @m # replaces the macro at compile-time with the expression a+2
より正確には、マクロが定義されたモジュールのトップレベル内でのみ動作します。
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
はこれが起こらないようにしますが、常にデフォルトを使用するように設定することは、言語デザインに反します。このための優れた防御策は、マクロ内で名前を使用したり、マクロ内に名前を導入しないようにすることです。これにより、人間の読者に追跡するのが難しくなります。
Python `dict` /` Dict`リテラルの構文のようなJSON。
前書き
Juliaは、辞書に次の構文を使用します。
Dict({k₁ => v₁, k₂ => v₂, …, kₙ₋₁ => vₙ₋₁, kₙ => vₙ)
PythonとJSONは次のようになります。
{k₁: v₁, k₂: v₂, …, kₙ₋₁: vₙ₋₁, kₙ: vₙ}
説明のために、Juliaでこの構文を使用し、新しい意味を追加することもできます( Dict
構文はJuliaの慣用方法です)。
まず、どのような表現であるかを見てみましょう:
julia> parse("{1:2 , 3: 4}") |> Meta.show_sexpr
(:cell1d, (:(:), 1, 2), (:(:), 3, 4))
つまり、 :cell1d
式を変換して変換するか、次のような新しい式を返す必要があります。
julia> parse("Dict(1 => 2 , 3 => 4)") |> Meta.show_sexpr
(:call, :Dict, (:(=>), 1, 2), (:(=>), 3, 4))
マクロ定義
以下のマクロは単純なものですが、そのようなコード生成と変換を実証することができます:
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
結果として得られるマクロ展開を確認しましょう:
julia> :(@dict {"a": :b, 'c': 1, :d: 2.0}) |> macroexpand
:(Dict("a" => :b,'c' => 1,:d => 2.0))
使用法
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
最後の例は、次のものとまったく同じです。
Dict(
"string" => :b,
'c' => 1,
:symbol => π,
Function => print,
(1:10) => range(1, 10)
)
誤解
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ₙ}`
ジュリアは、コロンのために他の用途があることに注意してください:
のようなあなたは、カッコで範囲リテラル式をラップするか、使用する必要がありますrange
例えば、機能を。