在 GitHub 上編輯此頁面

給定的實例

已給予實例(或簡稱「已給予」)定義特定類型的「正規」值,用於合成 內容參數 的引數。範例

trait Ord[T]:
  def compare(x: T, y: T): Int
  extension (x: T)
    def < (y: T) = compare(x, y) < 0
    def > (y: T) = compare(x, y) > 0

given intOrd: Ord[Int] with
  def compare(x: Int, y: Int) =
    if x < y then -1 else if x > y then +1 else 0

given listOrd[T](using ord: Ord[T]): Ord[List[T]] with

  def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match
    case (Nil, Nil) => 0
    case (Nil, _) => -1
    case (_, Nil) => +1
    case (x :: xs1, y :: ys1) =>
      val fst = ord.compare(x, y)
      if fst != 0 then fst else compare(xs1, ys1)

此程式碼定義一個具有兩個已給予實例的特質 OrdintOrd 定義 Ord[Int] 類型的已給予,而 listOrd[T] 定義 Ord[List[T]] 的已給予,適用於所有類型 T,這些類型本身附帶 Ord[T] 的已給予實例。listOrd 中的 using 子句定義一個條件:必須有類型 Ord[T] 的已給予,才能存在類型 Ord[List[T]] 的已給予。此類條件會由編譯器擴充為 內容參數

匿名已給予

已給予的名稱可以省略。因此,上一節的定義也可以這樣表示

given Ord[Int] with
  ...
given [T](using Ord[T]): Ord[List[T]] with
  ...

如果已給予的名稱不存在,編譯器會從已實作的類型中合成一個名稱。

注意:編譯器合成的名稱選擇為易讀且相當簡潔。例如,以上兩個實例將取得名稱

given_Ord_Int
given_Ord_List

合成名稱的精確規則請參閱此處。這些規則無法保證「過於相似」類型的給定實例之間沒有名稱衝突。若要避免衝突,可以使用已命名實例。

注意:為了確保穩健的二進位相容性,公開的函式庫應優先使用已命名實例。

別名給定

別名可用於定義等於某個表達式的給定實例。範例

given global: ExecutionContext = ForkJoinPool()

這會建立一個類型為 ExecutionContext 的給定 global,解析為右手邊的 ForkJoinPool()。第一次存取 global 時,會建立一個新的 ForkJoinPool,然後將其傳回,供此存取和所有後續對 global 的存取使用。此操作是執行緒安全的。

別名給定也可以是匿名的,例如

given Position = enclosingTree.position
given (using config: Config): Factory = MemoizingFactory(config)

別名給定可以具有類型參數和內容參數,就像任何其他給定一樣,但它只能實作單一類型。

給定巨集

給定別名可以具有 inlinetransparent 修改項。範例

transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${
  // code producing a value of a subtype of Annotations
}

由於 mkAnnotationstransparent,因此應用程式的類型為其右手邊的類型,這可能是宣告結果類型 Annotations[A, T] 的適當子類型。

給定實例可以具有 inline 但不能具有 transparent 修改項,因為其類型已從簽章得知。範例

trait Show[T] {
  inline def show(x: T): String
}

inline given Show[Foo] with {
  /*transparent*/ inline def show(x: Foo): String = ${ ... }
}

def app =
  // inlines `show` method call and removes the call to `given Show[Foo]`
  summon[Show[Foo]].show(foo)

請注意,給定實例中的內聯方法可能是 transparent

給定實例的內聯不會內聯/複製給定的實作,它只會內聯該實例的實例化。這用於協助消除內聯後未使用的給定實例的死碼。

模式約束給定實例

給定實例也可以出現在模式中。範例

for given Context <- applicationContexts do

pair match
  case (ctx @ given Context, y) => ...

在上面的第一個片段中,透過列舉 applicationContexts,建立類別 Context 的匿名給定實例。在第二個片段中,透過與 pair 選擇器的上半部進行比對,建立一個名為 ctx 的給定 Context 實例。

在每種情況下,模式約束給定實例都包含 given 和類型 T。模式與類型歸屬模式 _: T 完全匹配相同的選擇器。

否定給定

Scala 2 關於歧義的令人費解的行為已用於實作隱式解析中「否定」搜尋的類比,其中查詢 Q1 在其他查詢 Q2 成功時會失敗,而如果 Q2 失敗,則 Q1 會成功。有了新的清理行為,這些技術就不再適用。但新的特殊類型 scala.util.NotGiven 現在直接實作否定。

對於任何查詢類型 QNotGiven[Q] 僅在對 Q 的隱式搜尋失敗時才會成功,例如

import scala.util.NotGiven

trait Tagged[A]

case class Foo[A](value: Boolean)
object Foo:
  given fooTagged[A](using Tagged[A]): Foo[A] = Foo(true)
  given fooNotTagged[A](using NotGiven[Tagged[A]]): Foo[A] = Foo(false)

@main def test(): Unit =
  given Tagged[Int]()
  assert(summon[Foo[Int]].value) // fooTagged is found
  assert(!summon[Foo[String]].value) // fooNotTagged is found

給定實例初始化

沒有類型或內容參數的給定實例會在需要時初始化,也就是在第一次存取時。如果給定有類型或內容參數,則會為每個參考建立新的實例。

語法

以下是給定實例的語法

TmplDef             ::=  ...
                     |   ‘given’ GivenDef
GivenDef            ::=  [GivenSig] StructuralInstance
                     |   [GivenSig] AnnotType ‘=’ Expr
                     |   [GivenSig] AnnotType
GivenSig            ::=  [id] [DefTypeParamClause] {UsingParamClause} ‘:’
StructuralInstance  ::=  ConstrApp {‘with’ ConstrApp} [‘with’ TemplateBody]

給定實例以保留字 given 和一個選用的簽章開始。簽章定義實例的名稱和/或參數。其後接 :。有 3 種給定實例

  • 結構實例包含一個或多個類型或建構函式應用,後接 with 和包含實例成員定義的範本主體。
  • 別名實例包含一個類型,後接 = 和一個右側運算式。
  • 抽象實例僅包含類型,其後不接任何東西。