給定的實例
已給予實例(或簡稱「已給予」)定義特定類型的「正規」值,用於合成 內容參數 的引數。範例
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)
此程式碼定義一個具有兩個已給予實例的特質 Ord
。intOrd
定義 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)
別名給定可以具有類型參數和內容參數,就像任何其他給定一樣,但它只能實作單一類型。
給定巨集
給定別名可以具有 inline
和 transparent
修改項。範例
transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${
// code producing a value of a subtype of Annotations
}
由於 mkAnnotations
是 transparent
,因此應用程式的類型為其右手邊的類型,這可能是宣告結果類型 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
現在直接實作否定。
對於任何查詢類型 Q
,NotGiven[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
和包含實例成員定義的範本主體。 - 別名實例包含一個類型,後接
=
和一個右側運算式。 - 抽象實例僅包含類型,其後不接任何東西。