サーチ…


構文

  • マクロ名(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と評価されている間に本体を実行しtrueuntilループを実装したい場合、条件が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.quotQuoteNode違いは何ですか?どのようなものを使用しますか?ほとんどすべての場合、その違いは関係ありません。 QuoteNode代わりにMeta.quotを使用するのは、たぶん少し安全Meta.quot 。しかし、その違いを調べることは、ジュリアの表現とマクロがどのように機能するかに役立ちます。

Meta.quotQuoteNodeの違いについて説明しました

経験則があります:

  • あなたが補間を必要としている場合、または補間をサポートしたい場合は、 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 、補間が必要な場合をQuoteNodeQuoteNodeを使用することは決して有害ではありません。しかし、その違いを理解することで、 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))はエラーです。 $(...)は演算ではなく、単独で何もしません。これは "これを補間する"引用構文が使用する記号。つまり、それは見積もり内にのみ存在します。

$ fooeval( 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得ますが、 xyはそうではありません。 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=:macroExpr ?それは文字列として"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_typedcode_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例えば、機能を。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow