斯卡拉树比赛案例

时间:2015-04-18 13:33:52

标签: scala macros tree

我对Scala相当陌生,对于编写宏并寻找一些帮助/建议非常陌生。我有以下代码......

trait ValidationRule
case class Required() extends ValidationRule
case class HasLength(l: Int) extends ValidationRule
case class Person(name: String)

myMacro[Person] { p => p.name.is(Required(), HasLength(255)) }

显然这里有一些丢失的代码,但这只是伪问题。

所以给定一个代表p => p.name.is(Required(), HasLength(255))的树,我试图写一个match/case来选择代表ValidationRule的所有表达式。类似的东西:

case TypeApply(Select(_, ....

任何人都可以建议最佳匹配案例,以便能够提取代表每个"所有"来自"中的ValidationRules是"方法

1 个答案:

答案 0 :(得分:2)

你应该肯定调查Quasiquotes

Quasiquotes用于做两件事:构建树,以及模式匹配树。它们允许您根据等效的Scala代码表达要使用的树。你让quasiquote库处理Scala代码映射到Tree图的方式,这是一件好事!

你可以在REPL中使用它们,尽管宏观世界中的结果可能略有不同:

scala> import scala.reflect.runtime.universe._
scala> showRaw(cq"p => p.name.is(Required(), HasLength(255))")

res0: String = CaseDef(
  Bind(
    TermName("p"), 
    Ident(termNames.WILDCARD)), 
  EmptyTree, 
  Apply(
    Select(
      Select(
        Ident(TermName("p")), 
        TermName("name")), 
      TermName("is")), 
    List(
      Apply(
        Ident(TermName("Required")), 
        List()), 
      Apply(
        Ident(TermName("HasLength")), 
        List(Literal(Constant(255)))))))

使用Quasiquotes可以做的另一件事是实际使用它们进行模式匹配。

scala> val fromTree = cq"p => p.name.is(Required(), HasLength(255))"
scala> val cq"p => p.name.is($x, $y)" = fromTree


x: reflect.runtime.universe.Tree = Required()
y: reflect.runtime.universe.Tree = HasLength(255)

现在,您必须要小心,因为如果用户将其模式变量命名为p,则该模式仅匹配。

scala> val fromTree = cq"x => x.name.is(Required(), HasLength(255))"
scala> val cq"p => p.name.is($x, $y)" = fromTree

scala.MatchError: case (x @ _) => x.name.is(Required(), HasLength(255)) (of class scala.reflect.internal.Trees$CaseDef)
  ... 33 elided

相反,你会想要更通用一些:

scala> val cq"${p1:TermName} => ${p2:TermName}.name.is($x, $y)" = fromTree
p1: reflect.runtime.universe.TermName = x
p2: reflect.runtime.universe.TermName = x
x: reflect.runtime.universe.Tree = Required()
y: reflect.runtime.universe.Tree = HasLength(255)

scala> p1 == p2
res2: Boolean = true

当然,如果您在模式匹配中执行此操作,则可以执行以下操作:

case cq"${p1:TermName} => ${p2:TermName}.name.is($x, $y)" if p1 == p2 => 
  ???

请记住,宏是一个深洞,黑洞。如果您刚开始使用,则需要花费大量时间来使宏代码正确无误。在此之后,期望花费大量时间处理边缘情况。