联合类型 - 更多细节
语法
从语法上讲,联合遵循与交集相同的规则,但优先级较低,请参阅 交集类型 - 更多细节.
与模式匹配语法的交互
|
也用于模式匹配以分隔模式备选方案,并且优先级低于 :
(在类型化模式中使用),这意味着
case _: A | B => ...
仍然等效于
case (_: A) | B => ...
而不是
case _: (A | B) => ...
子类型规则
-
对于所有
A
、B
,A
始终是A | B
的子类型。 -
如果
A <: C
且B <: C
,则A | B <: C
-
与
&
类似,|
是可交换的和可结合的A | B =:= B | A A | (B | C) =:= (A | B) | C
-
&
对|
是分配的A & (B | C) =:= A & B | A & C
从这些规则可以得出,一组类型的最小上界 (LUB) 是这些类型的并集。这取代了 Scala 2 规范中最小上界的定义。
动机
在 Scala 中引入联合类型的首要原因是,它们允许我们保证对于每组类型,我们始终可以形成一个有限的 LUB。这在实践中(Scala 2 中的无限 LUB 以一种特殊的方式近似,导致类型不精确,有时会非常长)和理论上(Scala 3 的类型系统基于 DOT 演算,它具有联合类型)都是有用的。
此外,联合类型在尝试为现有的动态类型 API 提供类型时是一个有用的结构,这就是为什么它们是 TypeScript 的组成部分,甚至在 Scala.js 中部分实现。
联合类型的连接
在下面描述的某些情况下,可能需要将联合类型扩展为非联合类型,为此,我们将联合类型 T1 | ... | Tn
的连接定义为 T1
、...、Tn
的基类实例的最小交集类型。请注意,联合类型可能仍然作为类型参数出现在结果类型中,这保证了连接始终是有限的。
联合类型的可见连接是其连接,其中所有作为 透明 特性或类的实例的交集操作数都被删除。
示例
给定
trait C[+T]
trait D
trait E
transparent trait X
class A extends C[A], D, X
class B extends C[B], D, E, X
A | B
的连接是 C[A | B] & D & X
,A | B
的可见连接是 C[A | B] & D
。
硬联合类型和软联合类型
我们区分硬联合类型和软联合类型。硬联合类型是在源代码中显式编写的联合类型。例如,在
val x: Int | String = ...
Int | String
将是一个硬联合类型。软联合类型是由于对表达式备选方案进行类型检查而产生的类型。例如,表达式的类型
val x = 1
val y = "abc"
if cond then x else y
是软联合类型 Int | String
。匹配表达式也是如此。表达式的类型
x match
case 1 => x
case 2 => "abc"
case 3 => List(1, 2, 3)
是软联合类型 Int | "abc" | List[Int]
。
类型推断
在推断定义(val
、var
或 def
)的结果类型时,如果要推断的类型是软联合类型,则将其替换为其可见连接,前提是它不为空。类似地,在实例化类型参数时,如果相应的类型参数没有被联合类型上界限制,并且要实例化的类型是软联合类型,则将其替换为其可见连接,前提是它不为空。这反映了对单例类型的处理,除非明确指定,否则它们也会被扩展到其底层类型。其动机相同:推断“过于精确”的类型可能会导致以后出现不直观的类型检查问题。
示例
import scala.collection.mutable.ListBuffer
val x = ListBuffer(Right("foo"), Left(0))
val y: ListBuffer[Either[Int, String]] = x
这段代码可以进行类型检查,因为推断出的 x
右侧 ListBuffer
的类型参数是 Left[Int, Nothing] | Right[Nothing, String]
,它被扩展为 Either[Int, String]
。如果编译器没有进行这种扩展,最后一行将无法进行类型检查,因为 ListBuffer
对其参数是不变的。
成员
联合类型的成员是其连接的成员。
示例
以下代码无法进行类型检查,因为方法 hello
不是 AnyRef
的成员,而 AnyRef
是 A | B
的连接。
trait A { def hello: String }
trait B { def hello: String }
def test(x: A | B) = x.hello // error: value `hello` is not a member of A | B
另一方面,以下代码将被允许
trait C { def hello: String }
trait A extends C with D
trait B extends C with E
def test(x: A | B) = x.hello // ok as `hello` is a member of the join of A | B which is C
穷举检查
如果模式匹配的选择器是联合类型,则如果覆盖了联合的所有部分,则匹配被认为是穷举的。
擦除
A | B
的擦除类型是 A
和 B
的擦除类型的擦除最小上界。引用 TypeErasure#erasedLub
文档,擦除 LUB 的计算方式如下