此文件頁面特定於 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
。這種設計模式稱為「類型類別」。您可以在 “Type Classes as Objects and Implicits” 中進一步了解相關資訊。
準引號原生支援的許多資料類型,即使有 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
類型的值在執行階段取消引號時,它就會自動轉換為案例類別建構函式的呼叫。在此範例中有三個您應該考慮的重要重點
-
Liftable
伴隨物件包含一個輔助apply
方法,用於簡化Liftable
實例的建立。它採用單一類型參數T
和T => Tree
函式作為單一值參數,並傳回Liftable[T]
。 -
在此我們僅為執行時期反射定義
Liftable
。如果您嘗試從巨集使用它,將找不到它,因為每個宇宙都包含自己的Liftable
,而這些Liftable
彼此不相容。這個問題是由於當前反射 API 的路徑依賴性所造成的。(請參閱 在宇宙之間共用可提升實作) -
由於缺乏 衛生,對
Point
伴侶的參考必須完全限定,以確保此樹在每個可能的環境中都正確無誤。解決此參考問題的另一種方法是改用符號val PointSym = symbolOf[Point].companionModule implicit val lift = Liftable[Point] { p => q"$PointSym(${p.x}, ${p.y})" }
標準可提升項目
類型 | 值 | 表示法 |
---|---|---|
Byte 、Short 、Int 、Long |
0 |
q"0" |
Float |
0.0 |
q"0.0" |
Double |
0.0D |
q"0.0D" |
Boolean |
true 、false |
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})"
}
// ...
}