Scala 3 中的巨集

最佳實務

語言
此文件頁面專屬於 Scala 3,且可能涵蓋 Scala 2 中沒有的新概念。除非另有說明,否則此頁面中的所有程式碼範例都假設您使用的是 Scala 3。

內嵌

內嵌時請小心效能

若要充分利用 JVM JIT 最佳化,請避免產生大型方法。

巨集

即將推出

引號程式碼

保持引號可讀

  • 盡量避免在內部使用任意表達式的 ${...}
    • 使用 $someExpr
    • 使用 ${ someExprFrom('localExpr) }

舉例來說,請考慮下列範例

val sc: StringContext = ...
'{ StringContext(${Varargs(sc.parts.map(Expr(_)))}: _*) }

我們可以改寫成下列範例

val sc: StringContext = ...
val partExprs = sc.parts.map(Expr(_))
val partsExpr = Varargs(partExprs)
'{ StringContext($partsExpr: _*) }

在第二個範例中,引號的內容清晰許多。

避免巢狀內容

請考慮下列程式碼

val y: Expr[Int] = ...
def body(x: Expr[Int])(using quotes.Nested) =  '{ $x + $y }
'{ (x: Int) => ${ body('x) } }

改用一般內容並傳遞所有需要的表達式。這也有好處,就是函式不必在本地定義。

def body(x: Expr[Int], y: Expr[Int])(using Quotes) =
  '{ $x + $y }

val y: Expr[Int] = ...
'{ (x: Int) => ${ body('x, y) } }

引號反映

對於此區段,請考慮下列設定

object Box:
  sealed trait Base
  case class Leaf(x: Int) extends Base

// Quotes in contextual scope
val boxTpe : TypeRepr = TypeRepr.of[Box.type]
val baseTpe: TypeRepr = TypeRepr.of[Box.Base]
val baseSym: Symbol   = baseTpe.typeSymbol
val leafTpe: TypeRepr = TypeRepr.of[Box.Leaf]
val leafSym: Symbol   = leafTpe.typeSymbol

避免 Symbol.tree

在物件 sym: Symbol 上,sym.tree 會傳回與符號相關聯的 Tree。使用此方法時請小心,因為符號的樹狀結構可能未定義。當與符號相關聯的程式碼在與此存取不同的時間定義時,如果未使用 -Yretain-trees 編譯選項,則符號的 tree 將無法使用。源自 Java 程式碼的符號沒有相關聯的 tree

Symbol 取得 TypeRepr

在先前的標題中,我們看到應避免使用 Symbol.tree,因此不應在 sym: Symbol 上使用 sym.tree.tpe。因此,建議在 tpe: TypeRepr 物件上使用 tpe.memberType,以取得與 Symbol 對應的 TypeRepr

我們可以用兩種方式取得 LeafTypeRepr

  1. TypeRepr.of[Box.Leaf]
  2. boxTpe.memberType(leafSym)(換句話說,我們要求 Box 的成員 TypeRepr,其符號等於 leafSym 的符號。)

雖然這兩種方法是等效的,但只有在您已經知道正在尋找類型 Box.Leaf 時,才能使用第一種方法。第二種方法允許您探索未知的 API。

使用 Symbol 比較定義

在此處 深入了解 符號。

符號允許您使用 == 比較定義

leafSym == baseSym.children.head // Is true

然而,TypeRepr 上的 == 不會產生相同的結果

boxTpe.memberType(baseSym.children.head) == leafTpe // Is false

取得類型的符號

有一個便捷的捷徑可以取得 T 定義的符號。取代

TypeTree.of[T].tpe.typeSymbol

您可以使用

TypeRepr.of[T].typeSymbol

模式比對進入 API

模式比對是使用 API 的一種非常符合人體工學的方法。務必查看 *Module 物件中定義的 unapply 方法。

在巨集中搜尋內容範圍

您可以使用 Implicits.search 搜尋特定執行個體。

例如

def summonOrFail[T: Type]: Expr[T] =
  val tpe = TypeRepr.of[T]
  Implicits.search(tpe) match
    case success: ImplicitSearchSuccess =>
      val implicitTerm = success.tree
      implicitTerm.asExprOf[T]
    case failure: ImplicitSearchFailure =>
      reflect.report.throwError("Could not find an implicit for " + Type.show[T])

如果您正在撰寫巨集並偏好處理 ExprExpr.summonImplicits.search 的一個方便包裝器。

def summonOrFail[T: Type]: Expr[T] =
  Expr.summon[T] match
    case Some(imp) => imp
    case None => reflect.report.throwError("Could not find an implicit for " + Type.show[T])

此頁面的貢獻者