Scala 3 — 书籍

联合类型

语言
此文档页面特定于 Scala 3,可能涵盖 Scala 2 中不可用的新概念。除非另有说明,本页中的所有代码示例均假定您使用的是 Scala 3。

用于类型时,| 运算符会创建一个所谓的联合类型。类型 A | B 表示是类型 A 又是类型 B 的值。

在以下示例中,help 方法接受一个名为 id 的联合类型 Username | Password 的参数,它可以是 UsernamePassword

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

如果你尝试向 match 表达式添加一个不匹配 UsernamePassword 类型的 case,你也会收到一个错误

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 是同一种类型。

此页面的贡献者