類型變異
變異是類型系統中經常出現的主題。它用於決定類型參數在子類型化方面的行為方式。
首先,我們將設定幾個互相延伸的類別。
class Noun {}
class City extends Noun {}
class SanFrancisco extends City {}
我們在 泛型類型 部分中看到,可以使用變異符號來描述類型參數在輸出位置中使用時、在輸入位置中使用時,以及在任一位置中使用時的狀況。
我們將深入探討每一個案例。
共變
例如,考慮類型
type CovariantOf<X> = {
+prop: X;
getter(): X;
}
在此,X 嚴格出現在輸出位置:它用於讀取類型為 CovariantOf<X> 的物件 o 的資訊,透過屬性存取 o.prop 或呼叫 o.getter()。
值得注意的是,由於 prop 是唯讀屬性,因此無法透過對物件 o 的參照輸入資料。
當這些條件成立時,我們可以使用符號 + 來註解 CovariantOf 定義中的 X
type CovariantOf<+X> = {
+prop: X;
getter(): X;
}
這些條件對我們處理 CovariantOf<T> 類型的物件的方式有重要的影響,特別是關於子類型化。提醒一下,子類型化規則有助於我們回答這個問題:「在某些情況下,預期類型為 T 的值,傳入類型為 S 的值是否安全?」如果是這樣,則 S 是 T 的子類型。
使用我們的 CovariantOf 定義,並且由於 City 是 Noun 的子類型,因此 CovariantOf<City> 也是 CovariantOf<Noun> 的子類型。的確
- 當預期類型為
Noun的屬性時,讀取類型為City的屬性prop是安全的,而且 - 在呼叫
getter()時,當預期類型為Noun的值時,傳回類型為City的值是安全的。
結合這兩個條件,只要預期類型為 CovariantOf<Noun>,使用 CovariantOf<City> 永遠都是安全的。
協變用於常見範例 $ReadOnlyArray<T> 中。就像 prop 屬性一樣,無法使用 $ReadOnlyArray 參照將資料寫入陣列。這允許更彈性的子類型化規則:Flow 只需要證明 S 是 T 的子類型,即可確定 $ReadOnlyArray<S> 也是 $ReadOnlyArray<T> 的子類型。
不變性
讓我們看看如果我們試著放寬對 X 使用的限制,例如,讓 prop 成為可讀寫屬性,會發生什麼事。我們會得到類型定義
type NonCovariantOf<X> = {
prop: X;
getter(): X;
};
我們也宣告一個類型為 NonCovariantOf<City> 的變數 nonCovariantCity
declare const nonCovariantCity: NonCovariantOf<City>;
現在,將 nonCovariantCity 視為類型為 NonCovariantOf<Noun> 的物件是不安全的。如果我們允許這樣做,我們可以有以下宣告
const nonCovariantNoun: NonCovariantOf<Noun> = nonCovariantCity;
此類型允許以下指定
nonCovariantNoun.prop = new Noun;
這會使 nonCovariantCity 的原始類型無效,因為它現在會在 prop 欄位中儲存 Noun。
區分 NonCovariantOf 和 CovariantOf 定義的是類型參數 X 同時用於輸入和輸出位置,因為它用於讀取和寫入屬性 prop。這樣的類型參數稱為不變,並且是不變性的預設情況,因此不需要前置符號
type InvariantOf<X> = {
prop: X;
getter(): X;
setter(X): void;
};
假設一個變數
declare const invariantCity: InvariantOf<City>;
在以下情況中,使用 invariantCity不安全
- 需要一個
InvariantOf<Noun>,因為我們不應該能夠將Noun寫入屬性prop。 - 需要一個
InvariantOf<SanFrancisco>,因為讀取prop可能會傳回一個City,而它可能不是SanFrancisco。
換句話說,InvariantOf<City> 既不是 InvariantOf<Noun> 的子類型,也不是 InvariantOf<SanFrancisco> 的子類型。
反變性
當類型參數僅用於輸入位置時,我們說它以反變方式使用。這表示它只出現在我們將資料寫入結構的位置。我們使用符號 - 來描述這種類型參數
type ContravariantOf<-X> = {
-prop: X;
setter(X): void;
};
常見的反變位置是唯寫屬性和「設定器」函式。
類型為 ContravariantOf<City> 的物件可以在任何時候使用,只要預期類型為 ContravariantOf<SanFrancisco> 的物件,但不能在預期類型為 ContravariantOf<Noun> 時使用。換句話說,ContravariantOf<City> 是 ContravariantOf<SanFrancisco> 的子類型,但不是 ContravariantOf<Noun> 的子類型。這是因為將 SanFrancisco 寫入可以寫入任何 City 的屬性是可以的,但寫入任何 Noun 都是不安全的。