特定の部分関数を変換して新しい部分関数を作成するマクロを作成するのに問題があります。たとえば、指定された部分関数をその要素 (パターン バインダー、ガード条件、ボディ) に分解できるようにしたいと考えています。次に、パターン バインダーとガード条件を小さな断片に分解し、これらの断片から新しい部分関数を再構築します。しかし、デバッグできないマクロ展開で奇妙なエラーが発生します。
同じエラーを引き起こす最も単純な問題は、指定された部分関数をバインダー、ガード、および本体に分解し、それを同じ部分関数に再アセンブルするコードです。
単純な型ではこれを行うことができます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
およびpq
quasiquotesとのマッチング c.typecheck
orをさまざまな場所に追加する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をストレートマクロで使用しています(「マクロパラダイス」はありません)。