准引号

简介

语言
此文档页面专门针对 Scala 2 中提供的功能,这些功能已在 Scala 3 中删除或被替代功能所取代。除非另有说明,本页面中的所有代码示例均假定你使用的是 Scala 2。

Denys Shabalin 实验性

准引号是一种简洁的符号,可让你轻松操作 Scala 语法树

scala> val tree = q"i am { a quasiquote }"
tree: universe.Tree = i.am(a.quasiquote)

每次将代码片段包装在 q"..." 中时,它都会变成表示给定片段的树。你可能已经注意到,引号语法只是可扩展字符串插值的另一种用法,它在 2.10 中引入。尽管它们看起来像字符串,但它们在底层操作语法树。

相同的语法可用于将树作为模式进行匹配

scala> println(tree match { case q"i am { a quasiquote }" => "it worked!" })
it worked!

每当你使用准引号匹配树时,它将在给定树的结构等同于你作为模式提供的树时进行匹配。你可以借助 equalsStructure 方法手动检查结构相等性

scala> println(q"foo + bar" equalsStructure q"foo.+(bar)")
true

你还可以借助 $ 将内容放入准引号中

scala> val aquasiquote = q"a quasiquote"
aquasiquote: universe.Select = a.quasiquote

scala> val tree = q"i am { $aquasiquote }"
tree: universe.Tree = i.am(a.quasiquote)

此操作也称为取消引号。每当你取消准引号中类型为 Tree 的表达式时,它都会在结构上替换该树到该位置。大多数情况下,引号之间的此类替换等同于源代码的文本替换。

类似地,可以使用模式匹配中的取消引号在结构上解构树

scala> val q"i am $what" = q"i am { a quasiquote }"
what: universe.Tree = a.quasiquote

插值器

Scala 是一种具有丰富语法的语言,其语法会根据语法上下文而有很大差异

scala> val x = q"""
         val x: List[Int] = List(1, 2) match {
           case List(a, b) => List(a + b)
         }
       """
x: universe.ValDef =
val x: List[Int] = List(1, 2) match {
  case List((a @ _), (b @ _)) => List(a.$plus(b))
}

在此示例中,我们看到使用了三个主要上下文

  1. List(1, 2)List(a + b) 是表达式
  2. List[Int] 是一个类型
  3. List(a, b) 是一个模式

每个上下文都由一个单独的插值器覆盖

  用于
q 表达式定义导入
tq 类型
pq 模式

不同上下文之间的语法相似性并不意味着底层树之间的相似性

scala> println(q"List[Int]" equalsStructure tq"List[Int]")
false

如果我们窥视引擎盖,我们会看到树确实不同

scala> println(showRaw(q"List[Int]"))
TypeApply(Ident(TermName("List")), List(Ident(TypeName("Int"))))

scala> println(showRaw(tq"List[Int]"))
AppliedTypeTree(Ident(TypeName("List")), List(Ident(TypeName("Int"))))

类似地,模式和表达式也不等同

scala> println(pq"List(a, b)" equalsStructure q"List(a, b)")
false

为了构建有效的语法树,使用正确的插值器来完成这项工作非常重要。

此外,还有两个辅助插值器,它们允许你处理 Scala 语法的小区域

  用于
cq case 子句
fq for 循环枚举器

有关详细信息,请参阅 语法摘要 部分。

拼接

取消引用拼接是一种取消引用数量不定的元素的方法

scala> val ab = List(q"a", q"b")
scala> val fab = q"f(..$ab)"
fab: universe.Tree = f(a, b)

取消引用前带点注释表示一定程度的扁平化,称为拼接等级..$ 要求参数是 Iterable[Tree],而 ...$ 要求是 Iterable[Iterable[Tree]]

拼接可以轻松地与常规取消引用结合使用

scala> val c = q"c"
scala> val fabc = q"f(..$ab, $c)"
fabc: universe.Tree = f(a, b, c)

scala> val fcab = q"f($c, ..$ab)"
fcab: universe.Tree = f(c, a, b)

scala> val fabcab = q"f(..$ab, $c, ..$ab)"
fabcab: universe.Tree = f(a, b, c, a, b)

如果你想进一步抽象应用程序,可以使用 ...$

scala> val argss = List(ab, List(c))
arglists: List[List[universe.Ident]] = List(List(a, b), List(c))

scala> val fargss = q"f(...$argss)"
fargss: universe.Tree = f(a, b)(c)

目前 ...$ 拼接仅支持 defclass 定义中的函数应用程序和参数列表。

类似于构造,也可以使用 ..$...$ 来拆分树

scala> val q"f(..$args)" = q"f(a, b)"
args: List[universe.Tree] = List(a, b)

scala> val q"f(...$argss)" = q"f(a, b)(c)"
argss: List[List[universe.Tree]] = List(List(a, b), List(c))

在将拼接与常规 $ 变量提取相结合的方式上有一些限制

case q"f($first, ..$rest)" => // ok
case q"f(..$init, $last)"  => // ok
case q"f(..$a, ..$b)"      => // not allowed

因此,通常情况下,每个给定列表只允许一个 ..$。类似的限制也适用于 ...$

case q"f(..$first)(...$rest)" => // ok
case q"f(...$init)(..$first)" => // ok
case q"f(...$a)(...$b)"       => // not allowed

在本节中,我们只处理函数参数,但所有具有可变元素数的语法形式都遵循相同的拼接规则。语法摘要 和相应的详细信息部分演示了如何将拼接与其他语法形式一起使用。

此页面的贡献者