我在编写转换给定部分函数并创建新部分函数的宏时遇到问题。例如,我希望能够将给定的部分函数分解为其元素——模式绑定器、保护条件和主体;然后我想将模式绑定器和保护条件分解成更小的部分,并从这些部分中重新组装新的部分功能。但是,我在无法调试的宏扩展中遇到奇怪的错误。
给出相同错误的最简单问题是将给定的部分函数分解为 binder、guard 和 body,并将其重新组合成相同的部分函数的代码。
我可以使用简单类型来做到这一点,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
非工作代码:完全相同的宏,除了使用案例类而不是Int
作为部分函数的参数。
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
某处存在复合类型时,问题就会发生。我得到的错误消息与上面显示的这个大大简化的示例基本相同。
尽管尝试了许多转换部分函数的替代方法,但我似乎无法解决这个问题:
- 使用白盒宏上下文
- 使用普通的准引号,如上例所示
- 对 case和patterns 使用特殊的 quasiquotes
cq"..."
,如 quasiquotes 的文档中所示pq"..."
q"{case ..$cases}"
- 与警卫匹配:
q"{case $binder if $guard => $body }"
, 也与cq
和pq
quasiquotes - 在不同的地方添加
c.typecheck
orc.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(没有“宏天堂”)。