此文档页面特定于 Scala 2 中提供的功能,这些功能已在 Scala 3 中删除或被替代功能所取代。除非另有说明,此页面中的所有代码示例均假定你正在使用 Scala 2。
Denys Shabalin 实验性
空
q""
用于指示树的某些部分不是由用户提供的
Val
、Var
和没有右侧的Def
将其设置为q""
。- 没有边界的抽象类型定义将它们设置为
q""
。 - 没有
finally
子句的Try
表达式将其设置为q""
。 - 没有保护的
Case
子句将它们设置为q""
。
默认的 toString
格式将 q""
格式化为 <empty>
。
字面量
Scala 有许多默认内置字面量
q"1", q"1L" // integer literals
q"1.0f", q"1.0", q"1.0d" // floating point literals
q"true", q"false" // boolean literals
q"'c'" // character literal
q""" "string" """ // string literal
q"'symbol" // symbol literal
q"null" // null literal
q"()" // unit literal
所有这些值都属于 Literal
类型,除了符号,符号有不同的表示形式
scala> val foo = q"'foo"
foo: universe.Tree = scala.Symbol("foo")
借助 提升,您还可以直接从相应类型的变量轻松创建字面量树
scala> val x = 1
scala> val one = q"$x"
one: universe.Tree = 1
这适用于所有字面量类型(请参阅 标准可提升类型),除了 Null
。不支持将 null
值提升到 Null
类型;如果您确实想要创建一个 null
字面量,请使用 q"null"
scala> val x = null
scala> q"$x"
<console>:31: error: Can't unquote Null, bottom type values often indicate programmer mistake
q"$x"
^
在解构期间,您可以使用 解除 从 Literal
树中提取值
scala> val q"${x: Int}" = q"1"
x: Int = 1
同样,它适用于除 Null
之外的所有字面量类型。(请参阅 标准不可解除类型)
标识符和选择
标识符和成员选择是两个基本原语,可让您引用其他定义。这两个原语的组合也称为 RefTree
。
每个术语标识符由其名称及其是否使用反引号定义
scala> val name = TermName("Foo")
name: universe.TermName = Foo
scala> val foo = q"$name"
foo: universe.Ident = Foo
scala> val backquoted = q"`$name`"
backquoted: universe.Ident = `Foo`
尽管使用反引号和不使用反引号的标识符可能指代相同的事物,但它们在语法上并不等效
scala> val q"`Foo`" = q"Foo"
scala.MatchError: Foo (of class scala.reflect.internal.Trees$Ident)
... 32 elided
这是因为在模式匹配中,使用反引号的标识符具有不同的语义。
除了使用给定名称匹配标识符之外,您还可以借助 解除 提取其名称值
scala> val q"${name: TermName}" = q"Foo"
name: universe.TermName = Foo
此处名称归属很重要,因为如果没有它,您将获得等效于常规模式变量绑定的模式。
同样,您可以创建和提取成员选择
scala> val member = TermName("bar")
member: universe.TermName = bar
scala> val q"foo.$name" = selected
name: universe.TermName = bar
Super 和 This
可以使用 this
和 super
在继承链中选择精确的成员。
此树支持以下变体
scala> val q"$name.this" = q"this"
name: universe.TypeName =
scala> val q"$name.this" = q"foo.this"
name: universe.TypeName = foo
因此,不合格的 q"this"
等效于 q"${tpnme.EMPTY}.this"
。
类似地,对于 super
,我们有
scala> val q"$name.super[$qual].$field" = q"super.foo"
name: universe.TypeName =
qual: universe.TypeName =
field: universe.Name = foo
scala> val q"$name.super[$qual].$field" = q"super[T].foo"
name: universe.TypeName =
qual: universe.TypeName = T
field: universe.Name = foo
scala> val q"$name.super[$qual].$field" = q"other.super[T].foo"
name: universe.TypeName = other
qual: universe.TypeName = T
field: universe.Name = foo
应用程序和类型应用程序
值应用程序和类型应用程序是两个基本部分,可以从中构造对 Scala 函数和方法的调用。我们假设我们希望处理对以下方法的函数调用
def f[T](xs: T*): List[T] = xs.toList
这可以通过以下方法实现
scala> val apps = List(q"f[Int](1, 2)", q"f('a, 'b)")
scala> apps.foreach {
case q"f[..$ts](..$args)" =>
println(s"type arguments: $ts, value arguments: $args")
}
type arguments: List(Int), value arguments: List(1, 2)
type arguments: List(), value arguments: List(scala.Symbol("a"), scala.Symbol("b"))
如您所见,无论是否存在特定类型应用程序,我们都能够匹配这两个调用。发生这种情况是因为类型应用程序匹配器在树不是实际类型应用程序时提取空类型参数列表,从而可以统一处理这两种情况。
建议在匹配具有类型参数的函数时始终包含类型应用程序,因为即使用户没有明确编写它们,编译器也会在类型检查期间插入它们
scala> val q"$_; f[..$ts](..$args)" = toolbox.typecheck(q"""
def f[T](xs: T*): List[T] = xs.toList
f(1, 2, 3)
""")
ts: List[universe.Tree] = List(Int)
args: List[universe.Tree] = List(1, 2, 3)
Scala 方法调用的其他重要特性是多个参数列表和隐式参数
def g(x: Int)(implicit y: Int) = x + y
在这里,我们可能会得到一个或两个后续值应用程序
scala> val apps = List(q"g(1)", q"g(1)(2)")
scala> apps.foreach {
case q"g(...$argss)" if argss.nonEmpty =>
println(s"argss: $argss")
}
argss: List(List(1))
argss: List(List(1), List(2))
...$
在模式中允许我们贪婪地匹配所有后续值应用程序。与类型参数匹配器类似,需要小心,因为它即使在没有实际值应用程序的情况下也始终匹配
scala> val q"g(...$argss)" = q"g"
argss: List[List[universe.Tree]] = List()
因此,建议使用更具体的模式来检查以确保提取的 argss
不为空。
与类型参数类似,隐式值参数在类型检查期间会自动推断
scala> val q"..$stats; g(...$argss)" = toolbox.typecheck(q"""
def g(x: Int)(implicit y: Int) = x + y
implicit val y = 3
g(2)
""")
stats: List[universe.Tree] = List(def g(x: Int)(implicit y: Int): Int = x.+(y), implicit val y: Int = 3)
argss: List[List[universe.Tree]] = List(List(2), List(y))
分配和更新
分配和更新是显式更改变量或集合的两种相关方法
scala> val assign = q"x = 2"
assign: universe.Tree = x = 2
scala> val update = q"array(0) = 1"
update: universe.Tree = array.update(0, 1)
如您所见,更新语法只是语法糖,表示为对给定对象上的更新方法调用。
然而,准引号允许您根据面向用户的语法统一地解构它们
scala> List(assign, update).foreach {
case q"$left = $right" =>
println(s"left = $left, right = $right")
}
left = x, right = 2
left = array(0), right = 1
其中 array(0)
具有与函数应用程序相同的 AST。
另一方面,如果您想分别处理这两种情况,可以使用以下更具体的模式
scala> List(assign, update).foreach {
case q"${ref: RefTree} = $expr" =>
println(s"assign $expr to $ref")
case q"$obj(..$args) = $expr" =>
println(s"update $obj at $args with $expr")
}
assign 2 to x
update array at List(0) with 1
返回
return 表达式用于从函数中执行早期返回。
scala> val ret = q"return 2 + 2"
ret: universe.Return = return 2.$plus(2)
scala> val q"return $expr" = ret
expr: universe.Tree = 2.$plus(2)
抛出
throw 表达式用于抛出可抛出项
scala> val thr = q"throw new Exception"
thr: universe.Throw = throw new Exception()
scala> val q"throw $expr" = thr
expr: universe.Tree = new Exception()
归因
归属允许用户注释中间表达式的类型
scala> val ascribed = q"(1 + 1): Int"
ascribed: universe.Typed = (1.$plus(1): Int)
scala> val q"$expr: $tpt" = ascribed
expr: universe.Tree = 1.$plus(1)
tpt: universe.Tree = Int
注释
表达式可以被注释
scala> val annotated = q"(1 + 1): @positive"
annotated: universe.Annotated = 1.$plus(1): @positive
scala> val q"$expr: @$annot" = annotated
expr: universe.Tree = 1.$plus(1)
annot: universe.Tree = positive
重要的是,如果我们将注释与归属结合起来,这样的模式将不会匹配
scala> val q"$expr: @$annot" = q"(1 + 1): Int @positive"
scala.MatchError: (1.$plus(1): Int @positive) (of class scala.reflect.internal.Trees$Typed)
... 32 elided
在这种情况下,我们需要将它解构为 归属,然后将 tpt
解构为 带注释的类型。
元组
元组是具有内置用户友好语法的异构数据结构。语法本身只是映射到 scala.TupleN
调用的语法糖
scala> val tup = q"(a, b)"
tup: universe.Tree = scala.Tuple2(a, b)
目前,元组仅支持高达 22 的元数,但这只是一个实现限制,将来可能会解除。要找出是否支持给定的元数,请使用
scala> val `tuple 10 supported?` = definitions.TupleClass(10) != NoSymbol
tuple 10 supported?: Boolean = true
scala> val `tuple 23 supported?` = definitions.TupleClass(23) != NoSymbol
tuple 23 supported?: Boolean = false
尽管 Tuple1
类存在,但它没有内置语法。表达式周围的单括号不会改变它的含义
scala> val inparens = q"(a)"
inparens: universe.Ident = a
将 Unit
视为零元组也很常见
scala> val elems = List.empty[Tree]
scala> val nullary = q"(..$elems)"
nullary: universe.Tree = ()
准引号还支持任意元数的元组解构
scala> val q"(..$elems)" = q"(a, b)"
elems: List[universe.Tree] = List(a, b)
此模式还将表达式匹配为单元素元组
scala> val q"(..$elems)" = q"(a)"
elems: List[universe.Tree] = List(a)
以及 Unit
作为零元组
scala> val q"(..$elems)" = q"()"
elems: List[universe.Tree] = List()
块
块是用于表示一系列动作或绑定的基本基元。 q"..."
插值器等效于块。它允许你传达多个表达式,用分号或换行符分隔
scala> val t = q"a; b; c"
t: universe.Tree =
{
a;
b;
c
}
q"{...}"
和 q"..."
之间的唯一区别在于它们如何处理仅一个元素的情况。 q"..."
始终返回元素本身,而如果单个元素不是表达式,则块仍然保持为块
scala> val t = q"val x = 2"
t: universe.ValDef = val x = 2
scala> val t = q"{ val x = 2 }"
t: universe.Tree =
{
val x = 2;
()
}
块还可以使用 ..$
展平到其他块中
scala> val ab = q"a; b"
ab: universe.Tree =
{
a;
b
}
scala> val abc = q"..$ab; c"
abc: universe.Tree =
{
a;
b;
c
}
相同的语法可用于解构块
scala> val q"..$stats" = q"a; b; c"
stats: List[universe.Tree] = List(a, b, c)
解构始终返回块的用户定义内容
scala> val q"..$stats" = q"{ val x = 2 }"
stats: List[universe.Tree] = List(val x = 2)
由于使用表达式自动展平单元素块,因此表达式本身被认为是单元素块
scala> val q"..$stats" = q"foo"
stats: List[universe.Tree] = List(foo)
除了不被视为块的空树
scala> val q"..$stats" = q""
scala.MatchError: <empty> (of class scala.reflect.internal.Trees$EmptyTree$)
... 32 elided
零元素块等效于一个合成单元(由编译器插入,而不是由用户编写)
scala> val q"..$stats" = q"{}"
stats: List[universe.Tree] = List()
scala> val syntheticUnit = q"..$stats"
syntheticUnit: universe.Tree = ()
此类单元用于 if 的空 else
分支和 case 子句 的空主体,使其与零元素块一样方便地处理这些情况。
如果
if 表达式有两种类型:带有 else
子句的和不带子句的
scala> val q"if ($cond) $thenp else $elsep" = q"if (true) a else b"
cond: universe.Tree = true
thenp: universe.Tree = a
elsep: universe.Tree = b
scala> val q"if ($cond) $thenp else $elsep" = q"if (true) a"
cond: universe.Tree = true
thenp: universe.Tree = a
elsep: universe.Tree = ()
缺少 else
子句等效于包含合成单元文字(空块)的 else
子句。
模式匹配
模式匹配是 Scala 的基石功能,它允许你将值分解为其组件
q"$expr match { case ..$cases } "
其中 expr
是某个非空表达式,每个 case 都用 cq"..."
引用表示
cq"$pat if $expr => $expr"
两种形式的组合允许你构造和分解任意模式匹配
scala> val q"$expr match { case ..$cases }" =
q"foo match { case _: Foo => 'foo case _ => 'notfoo }"
expr: universe.Tree = foo
cases: List[universe.CaseDef] = List(case (_: Foo) => scala.Symbol("foo"), case _ => scala.Symbol("notfoo"))
scala> val cq"$pat1 => $body1" :: cq"$pat2 => $body2" :: Nil = cases
pat1: universe.Tree = (_: Foo)
body1: universe.Tree = scala.Symbol("foo")
pat2: universe.Tree = _
body2: universe.Tree = scala.Symbol("notfoo")
没有主体的 case 子句等效于包含合成单元文字(空块)的子句
scala> val cq"$pat if $expr1 => $expr2" = cq"_ =>"
pat: universe.Tree = _
expr1: universe.Tree = <empty>
expr2: universe.Tree = ()
没有保护的表示法借助 空表达式 表示。
尝试
try
表达式用于处理可能的错误条件,并通过 finally
确保一致的状态。错误处理情况和 finally
子句都是可选的。
scala> val q"try $a catch { case ..$b } finally $c" = q"try t"
a: universe.Tree = t
b: List[universe.CaseDef] = List()
c: universe.Tree = <empty>
scala> val q"try $a catch { case ..$b } finally $c" =
q"try t catch { case _: C => }"
a: universe.Tree = t
b: List[universe.CaseDef] = List(case (_: C) => ())
c: universe.Tree = <empty>
scala> val q"try $a catch { case ..$b } finally $c" =
q"try t finally f"
a: universe.Tree = t
b: List[universe.CaseDef] = List()
c: universe.Tree = f
类似于 模式匹配,case 可以使用 cq"..."
进一步分解。没有 finally
子句的表示法借助 空表达式 表示。
函数
有三种方法可以创建匿名函数
scala> val f1 = q"_ + 1"
anon1: universe.Function = ((x$4) => x$4.$plus(1))
scala> val f2 = q"(a => a + 1)"
anon2: universe.Function = ((a) => a.$plus(1))
scala> val f3 = q"(a: Int) => a + 1"
anon3: universe.Function = ((a: Int) => a.$plus(1))
第一种使用占位符语法。第二种命名函数参数,但仍然依赖类型推断来推断其类型。最后一种明确定义函数参数。由于实现限制,第二种表示法只能在括号中或另一个表达式中使用。如果你将它们省略,则必须指定参数类型。
参数表示为 Vals。如果你想以编程方式创建一个 val
,它应该具有推断的类型,你需要使用 空类型
scala> val tpt = tq""
tpt: universe.TypeTree = <type ?>
scala> val param = q"val x: $tpt"
param: universe.ValDef = val x
scala> val fun = q"($param => x)"
fun: universe.Function = ((x) => x)
所有给定的形式都以相同的方式表示,并且可以统一匹配
scala> List(f1, f2, f3).foreach {
case q"(..$params) => $body" =>
println(s"params = $params, body = $body")
}
params = List(<synthetic> val x$5 = _), body = x$5.$plus(1)
params = List(val a = _), body = a.$plus(1)
params = List(val a: Int = _), body = a.$plus(1)
你还可以进一步拆分参数
scala> val q"(..$params) => $_" = f3
params: List[universe.ValDef] = List(val a: Int = _)
scala> val List(q"$_ val $name: $tpt") = params
name: universe.TermName = a
tpt: universe.Tree = Int
建议您使用下划线模式代替 修饰符,即使您不打算将它们用作参数,它们也可能包含可能导致匹配失败的其他标志。
部分函数
部分函数是一种简洁的语法,它允许您通过模式匹配来表达具有有限域的函数
scala> val pf = q"{ case i: Int if i > 0 => i * i }"
pf: universe.Match =
<empty> match {
case (i @ (_: Int)) if i.$greater(0) => i.$times(i)
}
scala> val q"{ case ..$cases }" = pf
cases: List[universe.CaseDef] = List(case (i @ (_: Int)) if i.$greater(0) => i.$times(i))
树上“漂亮打印”视图的奇怪默认值表示它们与匹配表达式的树共享类似的数据结构这一事实。尽管如此,它们并不相互匹配
scala> val q”$expr match { case ..$cases }” = pf scala.MatchError: …
While 和 Do-While 循环
While 和 do-while 循环是低级控制结构,当特定迭代的性能至关重要时可以使用
scala> val `while` = q"while(x > 0) x -= 1"
while: universe.LabelDef =
while$6(){
if (x.$greater(0))
{
x.$minus$eq(1);
while$6()
}
else
()
}
scala> val q"while($cond) $body" = `while`
cond: universe.Tree = x.$greater(0)
body: universe.Tree = x.$minus$eq(1)
scala> val `do-while` = q"do x -= 1 while (x > 0)"
do-while: universe.LabelDef =
doWhile$2(){
x.$minus$eq(1);
if (x.$greater(0))
doWhile$2()
else
()
}
scala> val q"do $body while($cond)" = `do-while`
body: universe.Tree = x.$minus$eq(1)
cond: universe.Tree = x.$greater(0)
For 和 For-Yield 循环
for
和 for-yield
表达式允许我们编写单子风格的解析,将其转化为对 map
、flatMap
、foreach
和 withFilter
方法的调用
scala> val `for-yield` = q"for (x <- xs; if x > 0; y = x * 2) yield x"
for-yield: universe.Tree =
xs.withFilter(((x) => x.$greater(0))).map(((x) => {
val y = x.$times(2);
scala.Tuple2(x, y)
})).map(((x$3) => x$3: @scala.unchecked match {
case scala.Tuple2((x @ _), (y @ _)) => x
}))
解析中的每个枚举器都可以用 fq"..."
插值器表示
scala> val enums = List(fq"x <- xs", fq"if x > 0", fq"y = x * 2")
enums: List[universe.Tree] = List(`<-`((x @ _), xs), `if`(x.$greater(0)), (y @ _) = x.$times(2))
scala> val `for-yield` = q"for (..$enums) yield y"
for-yield: universe.Tree
类似地,可以将 for-yield
解构回枚举器和正文的列表
scala> val q"for (..$enums) yield $body" = `for-yield`
enums: List[universe.Tree] = List(`<-`((x @ _), xs), `if`(x.$greater(0)), (y @ _) = x.$times(2))
body: universe.Tree = x
重要的是要注意 for
和 for-yield
不会相互匹配
scala> val q"for (..$enums) $body" = `for-yield`
scala.MatchError: ...
New
New 表达式允许您构造给定类型的实例,可能使用其他类型或定义对其进行精炼
scala> val q"new ..$parents { ..$body }" = q"new Foo(1) with Bar { def baz = 2 }"
parents: List[universe.Tree] = List(Foo(1), Bar)
body: List[universe.Tree] = List(def baz = 2)
有关详细信息,请参阅 模板 部分。
Import
Import 树由引用和选择器列表组成
scala> val q"import $ref.{..$sels}" = q"import foo.{bar, baz => boo, poison => _, _}"
ref: universe.Tree = foo
sels: List[universe.Tree] = List((bar @ _), $minus$greater((baz @ _), (boo @ _)), $minus$greater((poison @ _), _), _)
选择器被提取为与选择器在语法上相似的模式树
- 简单的标识符选择器表示为模式绑定:
pq"bar"
- 重命名选择器表示为细箭头模式:
pq"baz -> boo"
- 未导入选择器表示为细箭头,右侧为通配符:
pq"poison -> _"
- 通配符选择器表示为通配符模式:
pq"_"
类似地,一个构造从程序创建的选择器列表中导入回来
scala> val ref = q"a.b"
scala> val sels = List(pq"foo -> _", pq"_")
scala> val imp = q"import $ref.{..$sels}"
imp: universe.Import = import a.b.{foo=>_, _}