我对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
是"方法
答案 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 =>
???
请记住,宏是一个深洞,黑洞。如果您刚开始使用,则需要花费大量时间来使宏代码正确无误。在此之后,期望花费大量时间处理边缘情况。