0

特定の部分関数を変換して新しい部分関数を作成するマクロを作成するのに問題があります。たとえば、指定された部分関数をその要素 (パターン バインダー、ガード条件、ボディ) に分解できるようにしたいと考えています。次に、パターン バインダーとガード条件を小さな断片に分解し、これらの断片から新しい部分関数を再構築します。しかし、デバッグできないマクロ展開で奇妙なエラーが発生します。

同じエラーを引き起こす最も単純な問題は、指定された部分関数をバインダー、ガード、および本体に分解し、それを同じ部分関数に再アセンブルするコードです。

単純な型ではこれを行うことができますPartialFunction[Int,Any]が、ケース クラスを含む型ではできませんPartialFunction[MyCaseClass,Any]

動作するコードと動作しないコードを次に示します。

作業コード: 部分関数を取得し、準引用符を使用してそれを分解し、同じ関数を再度アセンブルして返します。

package sample

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

object MacroTest {
  type Simple = PartialFunction[Int, Any]

  def no_problem(pf: Simple): Simple = macro no_problemImpl
  def no_problemImpl(c: blackbox.Context)(pf: c.Expr[Simple]) = {
    import c.universe._

    val q"{ case $binder  => $body }" = pf.tree
    q"{ case $binder  => $body }"
  }
}

このマクロは、コンパイルしてテストに合格します。

import MacroTest._

val testPf: Simple = { case x => x + 1 }
testPf(2) shouldEqual 3 // passes

  // now do the same with macro:
val result = no_problem({ case x => x + 1 })
result(2) shouldEqual 3 // passes

Non-working codeInt :部分関数の引数としての代わりにケース クラスを使用することを除いて、まったく同じマクロです。

case class Blob(left: Int, right: Int)

type Complicated = PartialFunction[Blob, Any]

def problem(pf: Complicated): Complicated = macro problemImpl
def problemImpl(c: blackbox.Context)(pf: c.Expr[Complicated]) = {
    import c.universe._

    val q"{ case $binder  => $body }" = pf.tree
    q"{ case $binder  => $body }"
}

コードはまったく同じで、タイプが異なるだけです (ComplicatedではなくSimple)。

マクロ コードはコンパイルされますが、テストはコンパイルに失敗します (マクロ展開で失敗します)。

val blob = Blob(1,2)
val testPf: Complicated = { case Blob(x, y) => x + y }
testPf(blob) shouldEqual 3 // passes

  // now try the same with macro:
val result = problem({ case Blob(x, y) => x + y })
  // compile error when compiling the test code: 
Could not find proxy for case val x1: sample.Blob in List(value x1, method applyOrElse, <$anon: Function1>, value result, method apply, <$anon: Function0>, value <local MacroTestSpec>, class MacroTestSpec, package sample, package <root>) (currentOwner= value y )

問題を可能な限り最小限に単純化しましたが、それでも失敗します。私の実際のコードでは、型はより複雑で、部分関数にはガードがある場合があり、引数とガードを再配置することで部分関数のコードを変換します。ガードがない場合は変換を機能させることができますが、部分関数の引数がケース クラスの場合は機能しません。おそらく、ガードの存在は問題の根源ではありません。問題は、unapplyどこかに複合型がある場合に発生します。上記の非常に単純化された例で得られるのと本質的に同じエラー メッセージが表示されます。

部分関数を変換する多くの代替方法を試したにもかかわらず、この問題を解決できないようです。

  • ホワイトボックス マクロ コンテキストを使用する
  • 上記の例のように、プレーンな準引用符を使用します
  • ケースとパターンcq"..."には特別な準引用符を使用し、準引用符のドキュメントに示されているようにpq"..."q"{case ..$cases}"
  • guard: とのマッチングq"{case $binder if $guard => $body }"、またcqおよびpqquasiquotesとのマッチング
  • c.typecheckorをさまざまな場所に追加するc.untypecheck(これは以前は と呼ばれていましresetAllAttrsたが、現在は廃止されています)
  • 準引用符は使用しませんが、生の構文ツリーですべてを行います:Traverser生のツリー マッチングと共に使用case UnApply(Apply(Select(t@Ident(TermName(_)), TermName("unapply")), List(Ident(TermName("<unapply-selector>")))), List(binder)) if t.tpe <:< typeOf[Blob]します。
  • Identパターン マッチャーの をガード条件から取得したに置き換えてみてくださいIdent。また、その逆も同様です (これにより、型チェックの失敗により、「アサーションに失敗しました」という奇妙なエラーが発生します)。
  • 特定の型の代わりに使用Anyして、 を返しPartialFunction[Any,Any]たり、合計関数Function1[Blob,Any]などを使用したりします。
  • Tの代わりに型パラメータを使用しBlob、マクロをパラメータ化して受け入れますPartialFunction[T,Any]

助けていただければ幸いです。私はScala 2.11.8をストレートマクロで使用しています(「マクロパラダイス」はありません)。

4

1 に答える 1

2

Scala コンパイラの長年の問題にぶつかっていると思います。型チェックはいくつかのケース、特にSI-5465を使用するエクストラクタでは冪等ではありません。これに対する簡単な解決策はありませんが、2 つの回避策を提案できます。まず、問題を簡単に説明します。unapply

def マクロと型チェックの問題

定義マクロは、型チェック フェーズ中に展開されます。結果として、def マクロへの引数は型付きツリーです。適切に型付けされたツリーまたは型付けされていないツリーを返すことは許容されます。ただし、部分的に型付けされた(あなたの場合) またはが正しくないツリーを返すと、コンパイラがトリップする可能性が非常に高く、せいぜい型チェック エラーまたは後続のフェーズでエラーが発生します。quasiquotes は型なしツリーを生成することに注意してください。これらの悪い木はどのようにして発生するのでしょうか?

  • 部分的に型付けされたツリー- 通常、型付けされていないコードを型付けされた引数にラップするか、それらの一部を型付けされていないコードに置き換えます。多くの場合、それらを回避できますが、常にそうとは限りません。
  • 不正な型付けされたツリー-元の型情報を無効にする方法で型付き引数を再配置することにより、たとえば、1 つの引数を別の引数に接合します。これらは確実に問題を引き起こします。

回避策

問題が概念的であり、深く根ざしていることを理解していただければ幸いです。ただし、この問題を解決するには、次の 2 つの方法のいずれかを使用できます。

  1. ハッキーな解決策- 最終結果の String 表現を介してラウンドトリップを行います。

    c.parse(showCode(q"{ case $binder  => $body }"))
    

    showCodeuntypecheckが冪等でない場合でも、通常は解析可能なコードを出力します。もちろん、これによりコンパイル時のパフォーマンス オーバーヘッドが発生しますが、これはユース ケースで許容される場合と許容されない場合があります。

  2. 難しい解決策- 内部コンパイラ API を使用して、グルー コードを手動で型チェックします。これを行う方法を 1 回の投稿で説明することはできませんが、型、シンボル、およびそれらの所有者についてすべてを学ぶ必要があります。最悪の部分は、ツリーが可変wrt 型情報であることです。そのルートに進む場合は、scala/asyncのソース コードを参照することをお勧めします。

最善の方法は、おそらくマクロの記述を避けるか、のセマンティック APIscala.metaがリリースされて定義マクロに使用できるようになるまで待つことです。

于 2017-01-09T13:05:14.303 に答える