首页 文章

如何在Scala宏中匹配部分函数的案例定义模式?

提问于
浏览
1

作为宏的一部分,我想操纵部分函数的大小写定义 .

为此,我使用Transformer来操作partial函数的case定义,并使用Traverser来检查case定义的模式:

def myMatchImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)
    (expr: c.Expr[A])(patterns: c.Expr[PartialFunction[A, B]]): c.Expr[B] = {
  import c.universe._

  val transformer = new Transformer {
    override def transformCaseDefs(trees: List[CaseDef]) = trees map {
      case caseDef @ CaseDef(pattern, guard , body) => {
        // println(show(pattern))
        val traverser = new Traverser {
          override def traverse(tree: Tree) = tree match {
            // match against a specific pattern
          }
        }
        traverser.traverse(pattern)
      }
    }
  }

  val transformedPartialFunction = transformer.transform(patterns.tree)

  c.Expr[B](q"$transformedPartialFunction($expr)")
}

现在让我们假设,我想要匹配的有趣数据由类Data(它是对象Example的一部分)表示:

case class Data(x: Int, y: String)

现在调用下面示例中的宏

abstract class Foo
case class Bar(data: Data) extends Foo
case class Baz(string: String, data: Data) extends Foo

def test(foo: Foo) = myMatch(foo){
  case Bar(Data(x,y))    => y
  case Baz(_, Data(x,y)) => y
}

部分函数的大小写定义的模式由编译器转换如下(Foo,Bar和Baz类也是对象Example的成员):

(data: Example.Data)Example.Bar((x: Int, y: String)Example.Data((x @ _), (y @ _)))
(string: String, data: Example.Data)Example.Baz(_, (x: Int, y: String)Example.Data((x @ _), (y @ _)))

这是在上面的宏中使用show打印模式的结果(使用show),原始抽象语法树(使用showRaw打印)如下所示:

Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Bar)), List(Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Data)), List(Bind(newTermName("x"), Ident(nme.WILDCARD)), Bind(newTermName("y"), Ident(nme.WILDCARD))))))

Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Baz)), List(Ident(nme.WILDCARD), Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Data)), List(Bind(newTermName("x"), Ident(nme.WILDCARD)), Bind(newTermName("y"), Ident(nme.WILDCARD))))))

如何编写与这些树匹配的模式引用?

2 回答

  • 2

    首先,有一种特殊用于CaseDefs的quasiquotes,称为 cq

    override def transformCaseDefs(trees: List[CaseDef]) = trees map {
      case caseDef @ cq"$pattern if $guard => $body" => ...
    }
    

    其次,你应该使用 pq 来解构模式:

    pattern match {
       case pq"$name @ $nested" => ...
       case pq"$extractor($arg1, $arg2: _*)" => ...
       ...
    }
    

    如果您对用于模式匹配的树的内部感兴趣,则它们由 patvarTransformer 中定义的 patvarTransformer 创建 .

    另一方面,如果你正在使用 UnApply 树(这是在攻击后生成的),我有一个坏消息:quasiquotes目前不支持它们 . 按照SI-7789进行修复后会收到通知 .

  • 0

    Den Shabalin指出,在这个特定的环境中不能使用quasiquotes,我设法找到一个与部分函数的案例定义模式相匹配的模式 .

    关键问题是,我们要匹配的构造函数(在我们的示例中为 Data )存储在 Apply 节点的 TypeTree 中 . 匹配在 TypeTree 中包含的树有点棘手,因为这个类的唯一提取器( TypeTree() )对这个特定任务没有多大帮助 . 相反,我们必须使用 original 方法选择包装树:

    override def transform(tree: Tree) = tree match {
      case Apply(constructor @ TypeTree(), args) => constructor.original match {
        case Select(_, sym) if (sym == newTermName("Data")) => ...
      }
    }
    

    在我们的用例中,包装树是一个 Select 节点,我们现在可以检查该节点的符号是否是我们要查找的节点 .

相关问题