巨集

類型巨集

語言
此文件頁面特定於 Scala 2 中提供的功能,這些功能已在 Scala 3 中移除或由其他功能取代。除非另有說明,此頁面中的所有程式碼範例都假設您使用的是 Scala 2。

已過時

Eugene Burmako

在先前版本的 “巨集天堂” 中可用的類型巨集,在巨集天堂 2.0 中不再受支援。請參閱 天堂 2.0 公告,以取得說明和建議的移轉策略。

直覺

就像 def 巨集讓編譯器在看到某些方法的呼叫時執行自訂函數一樣,類型巨集讓人在使用某些類型時,可以掛接到編譯器。以下程式片段顯示 H2Db 巨集的定義和用法,它會產生代表資料庫中資料表的案例類別,以及簡單的 CRUD 功能。

type H2Db(url: String) = macro impl

object Db extends H2Db("coffees")

val brazilian = Db.Coffees.insert("Brazilian", 99, 0)
Db.Coffees.update(brazilian.copy(price = 10))
println(Db.Coffees.all)

完整的 H2Db 類型巨集原始碼已提供 在 GitHub,而本指南涵蓋其最重要的面向。首先,巨集會在編譯時連接到資料庫,以產生靜態類型資料庫包裝器(樹狀結構產生在 反射概觀 中說明)。然後它會使用 NEW c.introduceTopLevel API,將產生的包裝器插入編譯器維護的頂層定義清單中。最後,巨集會傳回 Apply 節點,它代表對產生的類別的超級建構函數呼叫。注意,類型巨集應擴充為 c.Tree,這與擴充為 c.Expr[T] 的 def 巨集不同。這是因為 Expr 代表術語,而類型巨集則擴充為類型。

type H2Db(url: String) = macro impl

def impl(c: Context)(url: c.Expr[String]): c.Tree = {
  val name = c.freshName(c.enclosingImpl.name).toTypeName
  val clazz = ClassDef(..., Template(..., generateCode()))
  c.introduceTopLevel(c.enclosingPackage.pid.toString, clazz)
  val classRef = Select(c.enclosingPackage.pid, name)
  Apply(classRef, List(Literal(Constant(c.eval(url)))))
}

object Db extends H2Db("coffees")
// equivalent to: object Db extends Db$1("coffees")

類型巨集可以透過傳回 Template 樹狀結構,來轉換其主機,而不是產生合成類別並擴充為對它的參考。在 scalac 內部,類別和物件定義都以薄封裝器表示為 Template 樹狀結構,因此透過擴充為範本,類型巨集有可能改寫受影響類別或物件的整個主體。您可以在 GitHub 上看到此技術的完整範例。

type H2Db(url: String) = macro impl

def impl(c: Context)(url: c.Expr[String]): c.Tree = {
  val Template(_, _, existingCode) = c.enclosingTemplate
  Template(..., existingCode ++ generateCode())
}

object Db extends H2Db("coffees")
// equivalent to: object Db {
//   <existing code>
//   <generated code>
// }

詳細資料

類型巨集代表 def 巨集和類型成員之間的混合體。一方面,它們的定義類似於方法(例如,它們可以有值引數、具有內容限制的類型參數等)。另一方面,它們屬於類型名稱空間,因此只能用於預期類型的地方(請參閱 GitHub 上的完整範例),它們只能覆寫類型或其他類型巨集等。

功能 def 巨集 類型巨集 類型成員
拆分為 def 和 impl
可以有值參數
可以有類型參數
… 具有變異註解
… 具有內容限制
可以重載
可以繼承
可以覆寫和被覆寫

在 Scala 程式中,類型巨集可以出現在五種可能的職責中:類型職責、已套用類型職責、父類型職責、新職責和註解職責。巨集所使用的職責(可以使用 NEW c.macroRole API 檢查)會影響其允許擴充清單的不同。

職責 範例 類別 非類別? 套用? 範本?
類型 def x: TM(2)(3) = ???
套用類型 class C[T: TM(2)(3)]
父類型 class C extends TM(2)(3)
new TM(2)(3){}
新建 new TM(2)(3)
註解 @TM(2)(3) class C

簡而言之,類型巨集的擴充套件會用它回傳的樹狀結構取代類型巨集的用法。若要找出擴充套件是否有意義,請在腦中將巨集的某個用法替換成其擴充套件,並檢查產生的程式是否正確。

例如,在 class C extends TM(2)(3) 中當 TM(2)(3) 用作類型巨集時,可以擴充套件為 Apply(Ident(TypeName("B")), List(Literal(Constant(2)))),因為這會產生 class C extends B(2)。不過,如果 TM(2)(3)def x: TM(2)(3) = ??? 中當作類型使用,相同的擴充套件就沒有意義,因為 def x: B(2) = ???(假設 B 本身不是類型巨集;如果是,它會遞迴擴充套件,而擴充套件的結果會決定程式的有效性)。

提示和技巧

產生類別和物件

使用類型巨集時,你可能會越來越常發現自己處於 reify 不適用的區域,如 StackOverflow 中所說明。在這種情況下,請考慮使用 準引號,這是巨集天堂中的另一項實驗性功能,作為手動樹狀結構建構的替代方案。

此頁面的貢獻者