聯合類型 - 更多詳細資訊
語法
在語法上,聯合遵循與交集相同的規則,但優先順序較低,請參閱 交集類型 - 更多詳細資訊。
與模式比對語法的互動
|
也用於模式比對中,以分隔模式選項,並且優先順序低於輸入類型化模式中使用的 :
,這表示
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
方法不是 A | B
的聯集 AnyRef
的成員。
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 的計算方式如下