【发布时间】:2017-01-08 23:24:28
【问题描述】:
我在编写转换给定偏函数并创建新偏函数的宏时遇到问题。例如,我希望能够将给定的部分函数分解为其元素——模式绑定器、保护条件和主体;然后我想将模式绑定器和保护条件分解成更小的部分,并从这些部分中重新组装新的部分功能。但是,我在无法调试的宏扩展中遇到奇怪的错误。
产生相同错误的最简单问题是将给定的部分函数分解为 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 的复合类型时,就会出现问题。我收到的错误消息与上面显示的这个大大简化的示例基本相同。
尽管尝试了许多转换偏函数的替代方法,但我似乎无法解决这个问题:
- 使用白盒宏上下文
- 使用普通的准引号,如上例所示
- 对案例和模式
cq"..."、pq"..."和q"{case ..$cases}"使用特殊的准引号,如准引号文档中所示 - 与后卫匹配:
q"{case $binder if $guard => $body }",也与cq和pqquasiquotes 匹配 - 在各个地方添加
c.typecheck或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(没有“宏天堂”)。
【问题讨论】:
-
{ case x@Blob(aaa: Int, bbb: Int) => x.left + x.right }这是工作,我也不知道为什么-_-
标签: scala macros scala-macros scala-quasiquotes partial-functions