此文档页面特定于 Scala 2 中提供的功能,这些功能已在 Scala 3 中删除或被替代方案取代。除非另有说明,本页面中的所有代码示例均假定您使用的是 Scala 2。
Denys Shabalin 实验性
提升是一种可扩展的方式,用于在准引号中取消自定义数据类型的引号。其主要用例是支持将 字面量 值和许多反射基元作为树取消引号
scala> val two = 1 + 1
two: Int = 2
scala> val four = q"$two + $two"
four: universe.Tree = 2.$plus(2)
此代码成功运行,因为 Int
默认情况下被认为是 Liftable
。 Liftable
类型只是一个具有单个抽象方法的特质,该方法定义了给定类型到树的映射
trait Liftable[T] {
def apply(value: T): Tree
}
只要有 Liftable[T]
的隐式值可用,就可以在准引号中取消 T
的引号。此设计模式称为类型类。您可以在 “类型类作为对象和隐式” 中阅读更多相关信息。
准引号本机支持的许多数据类型永远不会触发 Liftable
表示形式的使用,即使它可用:Tree
、Symbol
、Name
、Modifiers
和 FlagSet
的子类型。
还可以组合提升和取消引号拼接
scala> val ints = List(1, 2, 3)
scala> val f123 = q"f(..$ints)"
f123: universe.Tree = f(1, 2, 3)
scala> val intss = List(List(1, 2, 3), List(4, 5), List(6))
scala> val f123456 = q"f(...$intss)"
f123456: universe.Tree = f(1, 2, 3)(4, 5)(6)
在这种情况下,列表的每个元素都将单独提升,结果将在定义点处拼接。
自带
要为自己的数据类型定义树表示形式,只需提供一个隐式 Liftable
实例即可
package points
import scala.reflect.runtime.universe._
case class Point(x: Int, y: Int)
object Point {
implicit val lift = Liftable[Point] { p =>
q"_root_.points.Point(${p.x}, ${p.y})"
}
}
这样,每当 Point
类型的值在运行时取消引号时,它都会自动转换为 case 类构造函数调用。在此示例中,您应该考虑三个重要点
-
Liftable
伴随对象包含一个帮助器apply
方法,用于简化Liftable
实例的创建。它采用单个类型参数T
和T => Tree
函数作为单个值参数,并返回Liftable[T]
。 -
这里我们只为运行时反射定义了
Liftable
。如果您尝试从宏中使用它,将找不到它,因为每个 universe 都包含自己的Liftable
,它与其他 universe 不兼容。此问题是由当前反射 API 的路径相关性引起的。(请参阅 在 universe 之间共享 liftable 实现) -
由于缺少 卫生,对
Point
伴随对象的引用必须完全限定,以确保此树在每个可能上下文中都是正确的。解决此引用问题的另一种方法是改用符号val PointSym = symbolOf[Point].companionModule implicit val lift = Liftable[Point] { p => q"$PointSym(${p.x}, ${p.y})" }
标准 Liftable
类型 | 值 | 表示形式 |
---|---|---|
Byte 、Short 、Int 、Long |
0 |
q"0" |
Float |
0.0 |
q"0.0" |
Double |
0.0D |
q"0.0D" |
布尔值 |
true ,false |
q"true" ,q"false" |
字符 |
'c' |
q"'c'" |
单位 |
() |
q"()" |
字符串 |
"string" |
q""" "string" """ |
符号 |
'symbol |
q"'symbol" |
Array[T] † |
Array(1, 2) |
q"s.Array(1, 2)" ‡ |
Option[T] † |
Some(1) |
q"s.Some(1)" ‡ |
Vector[T] † |
Vector(1, 2) |
q"s.c.i.Vector(1, 2)" ‡ |
List[T] † |
List(1, 2) |
q"s.c.i.List(1, 2)" ‡ |
Map[K, V] † |
Map(1 -> 2) |
q"s.c.i.Map((1, 2))" ‡ |
Set[T] † |
Set(1, 2) |
q"s.c.i.Set(1, 2)" ‡ |
Either[L, R] † |
Left(1) |
q"s.u.Left(1)" ‡ |
TupleN[...] * † |
(1, 2) |
q"(1, 2)" |
TermName |
TermName("foo") |
q"foo" |
TypeName |
TypeName("foo") |
tq"foo" |
树 |
tree |
tree |
表达式 |
expr |
expr.tree |
类型 |
typeOf[Int] |
TypeTree(typeOf[Int]) |
TypeTag |
ttag |
TypeTree(ttag.tpe) |
常量 |
const |
Literal(const) |
(*) 元组的 Liftable 定义适用于 [2, 22]
范围内的所有 N
。
(†) 所有类型参数本身都必须是 Liftable。
(‡) s.
是 scala 的缩写,s.c.i.
是 scala.collection.immutable
的缩写,s.u.
是 scala.util.
的缩写。
在不同宇宙之间重用 Liftable 实现
由于当前反射 API 的路径依赖性,在宏和运行时宇宙之间共享相同的 Liftable
定义并非易事。一种可能的方法是在一个特征中定义 Liftable
实现,并为每个宇宙分别实例化它
import scala.reflect.api.Universe
import scala.reflect.macros.blackbox.Context
trait LiftableImpls {
val universe: Universe
import universe._
implicit val liftPoint = Liftable[points.Point] { p =>
q"_root_.points.Point(${p.x}, ${p.y})"
}
}
object RuntimeLiftableImpls extends LiftableImpls {
val universe: universe.type = universe
}
trait MacroLiftableImpls extends LiftableImpls {
val c: Context
val universe: c.universe.type = c.universe
}
// macro impls defined as a bundle
class MyMacro(val c: Context) extends MacroLiftableImpls {
// ...
}
因此,实际上,仅为手头的给定宇宙定义 Liftable
要容易得多
import scala.reflect.macros.blackbox.Context
// macro impls defined as a macro bundle
class MyMacros(c: Context) {
import c.universe._
implicit val liftPoint = Liftable[points.Point] { p =>
q"_root_.points.Point(${p.x}, ${p.y})"
}
// ...
}