跳到主要內容

類型變異

變異是類型系統中經常出現的主題。它用於決定類型參數在子類型化方面的行為方式。

首先,我們將設定幾個互相延伸的類別。

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 的值是否安全?」如果是這樣,則 ST 的子類型。

使用我們的 CovariantOf 定義,並且由於 CityNoun 的子類型,因此 CovariantOf<City> 也是 CovariantOf<Noun> 的子類型。的確

  • 當預期類型為 Noun 的屬性時,讀取類型為 City 的屬性 prop 是安全的,而且
  • 在呼叫 getter() 時,當預期類型為 Noun 的值時,傳回類型為 City 的值是安全的。

結合這兩個條件,只要預期類型為 CovariantOf<Noun>,使用 CovariantOf<City> 永遠都是安全的。

協變用於常見範例 $ReadOnlyArray<T> 中。就像 prop 屬性一樣,無法使用 $ReadOnlyArray 參照將資料寫入陣列。這允許更彈性的子類型化規則:Flow 只需要證明 ST 的子類型,即可確定 $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

區分 NonCovariantOfCovariantOf 定義的是類型參數 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 都是不安全的。