准引号

提升

语言
此文档页面特定于 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 默认情况下被认为是 LiftableLiftable 类型只是一个具有单个抽象方法的特质,该方法定义了给定类型到树的映射

trait Liftable[T] {
  def apply(value: T): Tree
}

只要有 Liftable[T] 的隐式值可用,就可以在准引号中取消 T 的引号。此设计模式称为类型类。您可以在 “类型类作为对象和隐式” 中阅读更多相关信息。

准引号本机支持的许多数据类型永远不会触发 Liftable 表示形式的使用,即使它可用:TreeSymbolNameModifiersFlagSet 的子类型。

还可以组合提升和取消引号拼接

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 类构造函数调用。在此示例中,您应该考虑三个重要点

  1. Liftable 伴随对象包含一个帮助器 apply 方法,用于简化 Liftable 实例的创建。它采用单个类型参数 TT => Tree 函数作为单个值参数,并返回 Liftable[T]

  2. 这里我们只为运行时反射定义了 Liftable。如果您尝试从宏中使用它,将找不到它,因为每个 universe 都包含自己的 Liftable,它与其他 universe 不兼容。此问题是由当前反射 API 的路径相关性引起的。(请参阅 在 universe 之间共享 liftable 实现

  3. 由于缺少 卫生,对 Point 伴随对象的引用必须完全限定,以确保此树在每个可能上下文中都是正确的。解决此引用问题的另一种方法是改用符号

    val PointSym = symbolOf[Point].companionModule
    implicit val lift = Liftable[Point] { p =>
      q"$PointSym(${p.x}, ${p.y})"
    }
    

标准 Liftable

类型 表示形式
ByteShortIntLong 0 q"0"
Float 0.0 q"0.0"
Double 0.0D q"0.0D"
布尔值 truefalse 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})"
  }

  // ...
}

此页面的贡献者