Scala 树匹配案例

Scala Tree Match Case

我对 Scala 还很陌生,对编写宏和寻找一些东西也很陌生 help/advise。我有以下代码...

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 到 select 代表 ValidationRule 的所有表达式。类似于:

case TypeApply(Select(_, ....

任何人都可以提出最佳匹配案例,以便能够从 "is" 方法中提取代表每个 "all" ValidationRules 的树列表吗?

你应该 绝对 研究 Quasiquotes

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

您可以在 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 => 
  ???

请记住,宏是一个又深又黑的洞。如果您刚刚起步,预计会花费大量时间让您的宏代码正确无误。之后,预计会花很多时间处理边缘情况。