Scala 3 — 書籍

聯合類型

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

用於類型時,| 算子會建立所謂的聯合類型。類型 A | B 代表值不是類型 A就是類型 B

在以下範例中,help 方法接受一個名為 id 的參數,其聯合類型為 Username | Password,可以是 Username,也可以是 Password

case class Username(name: String)
case class Password(hash: Hash)

def help(id: Username | Password) =
  val user = id match
    case Username(name) => lookupName(name)
    case Password(hash) => lookupPassword(hash)
  // more code here ...

我們透過使用樣式比對來區分兩個選項來實作 help 方法。

此程式碼是一個彈性和類型安全的解決方案。如果您嘗試傳入 UsernamePassword 以外的類型,編譯器會將其標示為錯誤

help("hi")   // error: Found: ("hi" : String)
             //        Required: Username | Password

如果您嘗試將 case 新增到與 UsernamePassword 類型不符的 match 表達式,也會產生錯誤

case 1.0 => ???   // ERROR: this line won’t compile

聯合類型的替代方案

如所示,聯合類型可用於表示數種不同類型的替代方案,而不需要這些類型成為自訂類別階層的一部分,也不需要明確包裝。

預先規劃類別階層

沒有聯合類型,就需要預先規劃類別階層,如下面的範例所示

trait UsernameOrPassword
case class Username(name: String) extends UsernameOrPassword
case class Password(hash: Hash) extends UsernameOrPassword
def help(id: UsernameOrPassword) = ...

預先規劃無法很好地擴充,因為例如 API 使用者的需求可能無法預見。此外,使用 UsernameOrPassword 等標記特徵來混淆類型階層也會使程式碼更難以閱讀。

標記聯合

另一種替代方案是定義一個獨立的列舉類型,如下所示

enum UsernameOrPassword:
  case IsUsername(u: Username)
  case IsPassword(p: Password)

列舉 UsernameOrPassword 代表 UsernamePassword標記聯集。然而,這種建模聯集的方式需要明確包裝和解包,例如,Username 不是 UsernameOrPassword 的子類型。

聯集類型的推論

編譯器僅在明確給予聯集類型時,才會將聯集類型指定給表達式。例如,給定這些值

val name = Username("Eve")     // name: Username = Username(Eve)
val password = Password(123)   // password: Password = Password(123)

此 REPL 範例顯示在將變數繫結到 if/else 表達式的結果時,如何使用聯集類型

scala> val a = if true then name else password
val a: Object = Username(Eve)

scala> val b: Password | Username = if true then name else password
val b: Password | Username = Username(Eve)

a 的類型是 Object,它是 UsernamePassword 的超類型,但不是最小超類型 Password | Username。如果您想要最小超類型,您必須明確給予,就像對 b 所做的那樣。

聯集類型是交集類型的對偶。就像交集類型的 &| 也是可交換的:A | BB | A 是同種類型。

此頁面的貢獻者