9

Quasiquotes是惊人的——它们大大减少了在 Scala 中编写宏的痛苦,而且根据我的经验,它们几乎总是完全按照我的预期工作。最重要的是,它们现在可以作为Scala 2.10 中的插件使用。

这个问题是关于我在写这篇博文时遇到的一个小问题。当我能找到几分钟的时间时,它就在我的清单上,但我想我会把它贴在这里,以防其他人能打败我,并帮助遇到同样问题的其他人。

假设我有一个名称类型对列表的列表:

val pss = List(
  List(newTermName("x") -> typeOf[Int], newTermName("y") -> typeOf[Char]),
  List(newTermName("z") -> typeOf[String])
)

我想把它们变成一棵看起来像这样的树:

def foo(x: Int, y: Char)(z: String) = ???

以下工作正常:

q"def bar(${pss.head.head._1}: ${pss.head.head._2}) = ???"

也就是说,它构建了以下树:

def bar(x: Int) = ???

这表明我应该能够写出这样的东西:

val quoted = pss.map(_.map { case (n, t) => q"$n: $t" })

q"def foo..${quoted.map(ps => q"($ps)")} = 1"

或者更简单一点,在一个参数列表中包含多个参数:

q"def baz(..${quoted.head}) = ???"

两者都不起作用 - 我收到这样的错误:

<console>:28: error: type mismatch;
 found   : List[c.universe.Typed]
 required: List[c.universe.ValDef]
           q"def baz(..${quoted.head}) = ???"
                                ^

很公平——我可以看到它对准引用器的看法,就像我在构建类型表达式而不是在quoted. 我想尝试的显而易见的事情都没有奏效(添加= _,明确地将准引号键入为 aValDef等)。

我知道我可以手动构建参数定义:

val valDefs = pss.map(
  _.map {
    case (n, t) => ValDef(Modifiers(Flag.PARAM), n, TypeTree(t), EmptyTree)
  }
)

现在baz版本(带有一个参数列表)可以工作:

q"def baz(..${valDefs.head}) = ???"

但不是foo版本(具有多个参数列表的版本)。

所以这里有两个问题。首先,如何使用 quasiquotes 将名称类型对转换ValDef为带引号的参数列表上下文之外的参数?其次,如何将参数定义列表转换​​为多个参数列表?

为整个该死的事情回退到手动 AST 构造很容易(请参阅我的帖子以获取示例),但我希望能够使用准引号代替。

4

1 回答 1

5

这是您的问题的快速解决方案:

val pss = List(
  List(newTermName("x") -> typeOf[Int], newTermName("y") -> typeOf[Char]),
  List(newTermName("z") -> typeOf[String])
)
val vparamss: List[List[ValDef]] = pss.map { _.map { case (name, tpe) => q"val $name: $tpe" } }
q"def foo(...$vparamss)"

如您所见,特殊的 ...$splice 允许您定义具有多个参数列表的函数。函数参数本身的表示方式与常规 val 相同。

...$it 可能有用的另一个示例:

val xy = List(List(q"x"), List(q"y"))
q"f(...$xy)" // same as q"f(x)(y)"
于 2013-09-05T19:08:54.957 回答