我在编写转换给定部分函数的宏并创建新的部分函数时遇到问题。例如,我希望能够将给定的部分函数分解为其元素 - 模式绑定,保护条件和正文;然后我想将图案活页夹和防护条件分解成更小的部分,并从这些部件中重新组装新的部分功能。但是,我在宏扩展时遇到奇怪的错误,我无法调试。
产生相同错误的最简单问题是将给定的部分函数分解为绑定器,防护装置和主体的代码,并将其重新组合回相同的部分函数。
我可以使用简单类型PartialFunction[Int,Any]
执行此操作,但不能使用涉及案例类的类型PartialFunction[MyCaseClass,Any]
。
这是有效的代码和没有的代码。
工作代码:采用部分函数,使用quasiquotes对其进行解构,再次组合相同的函数,然后将其返回。
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}"
使用特殊的quasiquotes,如quasiquotes文档中所示q"{case $binder if $guard => $body }"
,还有cq
和pq
quasiquotes 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直接宏(没有“宏天堂”)。
答案 0 :(得分:2)
我相信你在Scala编译器中遇到了长期存在的问题。在某些情况下,Typechecking不是幂等,特别是使用unapply
:SI-5465的提取器。对此没有简单的解决方案,但我可以建议两种解决方法。我先简单解释一下这个问题。
在类型检查阶段,Def宏被扩展。因此,def宏的参数是键入的树。返回良好类型的或无类型树是可以接受的。但是,返回部分类型(您的案例)或 ill-typed 树很可能会使编译器跳闸,最多导致类型检查错误或后续阶段出错。请注意,quasiquotes生成无类型树。这些坏树怎么会出现?
希望您可以看到问题是概念性的,并且根深蒂固。但您可以采用以下两种方法之一来解决问题:
hacky解决方案 - 通过最终结果的字符串表示进行往返:
c.parse(showCode(q"{ case $binder => $body }"))
showCode
通常会打印可解析的代码,即使untypecheck
不是幂等的。当然,这会产生一些编译时性能开销,这可能会或可能不会对您的用例可接受。
最好的办法是避免编写宏或等到scala.meta
的语义API发布后再将其用于def宏。