マクロとのパターンマッチング

Juliaは、Haskell、Prolog、Erlang、Scala、Mathematica言語でパターンマッチングとして証明されているプログラミング手法をサポートしていません。 しかし、この致命的な欠陥を修正できるマクロを作成できます。 次のようになります。

julia> immutable X a end julia> immutable Y a ; b end julia> @case(Y(X(9),2), Y(4,3)-> 55, Y(X(k),2)->1+k) 10
      
      





ソースコードはgithubで入手できます。

同様のもの(ただし、はるかに開発されており、すぐに使用できるもの)をここで取り上げることができますが、記事の例として分解するには大きすぎます。



Scalaの場合と同様に、認識される必要のある代替案ごとに、型が作成されます(この場合、関数型プログラミングの場合のように不変ですが、このコードは型でも機能します)。 必要に応じて、1つの要約から継承できます。



マクロが機能するジュリアコード 、抽象構文ツリー(AST)として表されます。 このツリーでは、リーフは単純な言語定数(数値、文字列)とシンボル(Symbol型)であり、ノードはフィールドheadとargsを持つExpr型です。 タイプExprまたはSymbolのオブジェクトを作成するには、構文シュガーを使用できます:: vは単なるシンボルであり、 :(1 + 2)Expr(:call ,: +、 1、2)を表します(Exprはコンストラクターの最初の引数を先頭に、残りを配列に配置しますargs)。 式を作成するとき、プログラムで作成された部分式を「引用」できます::($(a)+ 1) -式、変数aから単位式への部分式の追加。 引用(引用)はLisp言語で発明され、メタプログラミングをサポートする多くの言語で需要があることが証明されました。



「ケース」マクロは、解析された式を最初の引数として受け取り、残りは-サンプル->反応ペアを受け取ります。 これらの式がASTでどのように見えるかを見てみましょう。
 julia> :(Y(X(k),2)->1+k).head :-> julia> :(Y(X(k),2)->1+k).args 2-element Array{Any,1}: :(Y(X(k),2)) quote # none, line 1: 1 + k end
      
      







そのような式を処理するコードを検討してください。
  casev(v,np,e1) = let ...     spat if(e1.head == :(->)) (p,c) = e1.args if(p.head == :(call)) t = eval(p.args[1]) es = map(spat, t.names, p.args[2:end]) :(if(isa($v,$t)) ; $(pcomp(c,np,es)) else $np end) end end end
      
      





casev関数は引数を取ります:vは分析される式に関連付けられたシンボル(式自体ではなく、数回計算しないように)、e1はsample-> reaction、npはサンプルが認識されない場合の処理​​です( プログラミングを連想させる手法です)続編で )。

最初に、この式の形式が「->」であり、その引数が変数「p」(パターン)および「c」(それを処理するコード)に格納されていることを確認します。

呼び出しに類似したパターンは、文字タイプと引数式に解析されます。 タイプシンボルを使用して何ができるかを理解するには、タイプシンボルをタイプ自体に変換する必要があります(Juliaのタイプは「一次量」です)。 eval関数を使用してキャラクターを計算できます。 (彼女は現在のモジュールのコンテキストで式を計算するので、マクロと補助関数を別のモジュールに分離することに成功していません。)

次に、各ペアの「spat」関数を、このサブフィールドに対応するタイプフィールドの名前と呼びます。

  spat(n::Symbol, p::Symbol) = (:(=), :($p = $v.$n)) spat(n::Symbol, p::Expr) = (:(m), let s = gensym() ; (m) -> :(let $s = $v.$n $(casev(s,np,:($p->$m))) end) end) spat(n::Symbol, p::Any) = (:(==), :($p == $v.$n))
      
      





これは、サブサンプルとして分散されるマルチメソッドです。 SymbolおよびAnyタイプ(すべての定数がその下にあります)の場合、コードが生成され、後でそれをどうするかについてのメモが生成されます。 複雑なサブサンプル(Exprなど)の場合、クロージャーが作成され、サブサンプルの認識機能が再帰的に作成され、反応がなくなります(クロージャー引数は 'm')-現在のサンプルの処理がそこに転送されます。

サンプル処理を作成できるようになりました
 :(if(isa($v,$t)) ; $(pcomp(c,np,es)) else $np end)
      
      





「isa」は分析対象の値のタイプが「t」(サンプルから取得したシンボル)であることを確認し、「pcomp」はサンプルを認識するコードの一部を単一の式にコンパイルし、「np」はサンプルの残りを認識しない「継続」です認識されます。 このアプローチにより、認識エラーの可能性があるそれぞれの処理中に「継続」コードが複製されるという事実につながります。 このマクロが人間によって使用される限り、それは許容される贅沢です。 ロボットがパターンマッチングを使用してJuliaのコードの記述を開始する場合は、ローカル関数にしてそのシンボルを渡す必要があります。

  pcomp(c,np,p) = if(length(p) == 0) c else (r,d) = p[1] n1 = pcomp(c,np,p[2:end]) if(r == :(=)) :(let $d ; $n1 ; end) elseif(r == :(==)) :(if $(d) ; $n1 else $np end) elseif(r == :(m)) d(n1) end end
      
      





この関数は、サンプルの個々の部分を認識する断片の配列を取得します。 この配列が空の場合、生成されたプログラムのこの時点で、パターンはすでに認識されているため、(引数 'c'から)反応コードを呼び出す必要があります。

この場所のサンプルにシンボルがあった場合、この名前の変数で「let」ブロックを作成して初期化し、そこでさらに処理するためのコードを配置する必要があります。

定数があった場合は、対応する「if」を作成します(「else」の代替には、失敗した場合の「continue」コードが含まれます)。

そして、短絡が発生した場合、サンプルの残りを認識するためのコードを渡してそれを呼び出します-それは何をすべきかを理解します。



これで、いくつかの代替サンプルを処理してマクロ自体を実装する方法が明確になりました。
  casen(v,el) = if(length(el) == 0) :() else casev(v,casen(v,el[2:end]),el[1]) end macro case(v,e1...) if(isa(v,Symbol)) casen(v,e1) else let s = getsym() :(let $(s) = $(v) ; $(casen(s,e1)) end end end
      
      





ヒットするコード
読めない
 julia> macroexpand(:(@case(Y(X(9),2),Y(4,3)-> 55, Y(X(k),2)->1+k))) :(let #246###11039 = Y(X(9),2) # line 48: if isa(#246###11039,Y) # line 32: if 4 == #246###11039.a # line 11: if 3 == #246###11039.b # line 11: begin # none, line 1: 55 end else # line 11: if isa(#246###11039,Y) # line 32: let # line 21: #244###11040 = #246###11039.a # line 22: if isa(#244###11040,X) # line 32: let #245#k = #244###11040.a # line 9: begin # ADT.jl, line 22: if 2 == #246###11039.b # line 11: begin # none, line 1: 1 + #245#k end else # line 11: () end end end else # line 32: () end end else # line 32: () end end else # line 11: if isa(#246###11039,Y) # line 32: let # line 21: #244###11040 = #246###11039.a # line 22: if isa(#244###11040,X) # line 32: let #245#k = #244###11040.a # line 9: begin # ADT.jl, line 22: if 2 == #246###11039.b # line 11: begin # none, line 1: 1 + #245#k end else # line 11: () end end end else # line 32: () end end else # line 32: () end end else # line 32: if isa(#246###11039,Y) # line 32: let # line 21: #244###11040 = #246###11039.a # line 22: if isa(#244###11040,X) # line 32: let #245#k = #244###11040.a # line 9: begin # ADT.jl, line 22: if 2 == #246###11039.b # line 11: begin # none, line 1: 1 + #245#k end else # line 11: () end end end else # line 32: () end end else # line 32: () end end end)
      
      






All Articles