準引號

提升

語言
此文件頁面特定於 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。這種設計模式稱為「類型類別」。您可以在 “Type Classes as Objects and Implicits” 中進一步了解相關資訊。

準引號原生支援的許多資料類型,即使有 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 類型的值在執行階段取消引號時,它就會自動轉換為案例類別建構函式的呼叫。在此範例中有三個您應該考慮的重要重點

  1. Liftable 伴隨物件包含一個輔助 apply 方法,用於簡化 Liftable 實例的建立。它採用單一類型參數 TT => Tree 函式作為單一值參數,並傳回 Liftable[T]

  2. 在此我們僅為執行時期反射定義 Liftable。如果您嘗試從巨集使用它,將找不到它,因為每個宇宙都包含自己的 Liftable,而這些 Liftable 彼此不相容。這個問題是由於當前反射 API 的路徑依賴性所造成的。(請參閱 在宇宙之間共用可提升實作)

  3. 由於缺乏 衛生,對 Point 伴侶的參考必須完全限定,以確保此樹在每個可能的環境中都正確無誤。解決此參考問題的另一種方法是改用符號

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

標準可提升項目

類型 表示法
ByteShortIntLong 0 q"0"
Float 0.0 q"0.0"
Double 0.0D q"0.0D"
Boolean truefalse q"true"q"false"
Char 'c' q"'c'"
Unit () q"()"
String "string" q""" "string" """
Symbol '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 tree
Expr expr expr.tree
類型 typeOf[Int] TypeTree(typeOf[Int])
TypeTag ttag TypeTree(ttag.tpe)
Constant const Literal(const)

(*) 可提升元組的定義適用於 [2, 22] 範圍內的所有 N

(†) 所有類型參數本身都必須可提升。

(‡) s. 是 scala 的簡寫,s.c.i.scala.collection.immutable 的簡寫,s.u.scala.util. 的簡寫。

在不同宇宙之間重複使用可提升實作

由於當前反射 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})"
  }

  // ...
}

此頁面的貢獻者