在近代高等程式語言快(ㄏㄨˋ)速(ㄒ一ㄤ)演(ㄔㄠ)化(ㄒㄧˊ)的狀況下,很多 strong type 的程式語言都開始有了 generic 的設計, 舉例來說:
- Mobile: Swift, Kotlinn
- Frontend: Flowtype, TypeScript
- 後端語言:Python (pyre), Scala, Java (沒錯!現在連 Java 都有)
Generic type 雖然看起來是比較新的東西,但如果要做到嚴格的 strong type 基本上是少不了的。 例如 Array/List 如果沒有 generic 的話,只能有兩種做法:
- 把所有東西都轉成 Object or Any type 才能塞進 List,然後在從 List 取出物件的時候再做 down casting。
- 針對需要的 object type 個別實作相對應的 List type,例如:Cat 就有 CatList。
第一個方法的問題是:你有可能 down cast 到錯的 type,譬如說你本來塞的是 Cat,但你拿出來後 cast 成 Dog。 這樣就會噴 Runtime Error。而第二種方法的確是 type safe ,但因為每個 type 都要重寫一堆邏輯,非常不方便。
現在我們知道 generic 的方便性及重要性了。接下來我們來進入本篇想討論的:Covariance 跟 Contravariance。
基本上, generic 的出現代表了 type system 增加了一個新的維度,碰上有 subtype 的 type system 會變得比平常複雜。 偏偏現代幾乎所有程式語言都有 inheritance,所以這變成了現在 programmer 早晚會碰到的問題。我聽到的 case 都幾乎是走 「碰到就想辦法繞過」這種解決方式。而個人在工作上也有碰到因為對 generic 一知半解而設計出來得詭異架構。要了解什麼是 covariance ,我們必須先了解 generic 碰上 subtype 會出現什麼樣的問題,其中最經典的:
乍看之下答案是「當然是啊,Cat 是 Animal,當然 List<Cat> 就是 List<Animal> 啊」,但實際上並沒有這麼簡單。
我們先假設 List<Cat> 是 List<Animal> 這件事情成立好了,這就代表:
List<Cat> listOfCat = // 總之,某個方法生出來的,不是重點所以略過
List<Animal> listOfAnimal = listOfCat;以上,根據假設沒問題,但接下來的 code 就不合理了:
listOfAnimal.add(new Dog());光看這一行可能沒問題,但同時考慮前段 code 就能看出錯誤:listOfAnimal 實際上是 listOfCat,而其 type 是 List<Cat>。
也就是,你正在把 dog 塞進一個只能放 Cat 的 List,而如果其他地方還持有 listOfCat 的 reference 的話,就有可能拿到 dog,
而 dog 不是 cat,當然就是個 type error。
既然如此,那反過來說
答案是....看情況。
那麼什麼情況下不行呢?
如果你今天試著從 list 拿值出來就不行。
Cat cat = listOfCat.get(0) // 取得 Animal
因為 listOfCat 如果是 listOfAnimal 的 supertype 的話,代表 listOfCat 有可能實際上是 listOfAnimal,
也就是你取到的值有可能是 Animal 而不是 Cat,而 Animal 並不是 Cat 的 subtype,於是這邊會產生另外一個 type error。
所以以常識上的通用 List 來說的話,List<Cat> 既不是 List<Animal> 的 subtype 也不是 supertype。
那假設我們想做一個特殊的 List 使得 List<Cat> 是 List<Animal> 的 subtype 呢?
首先,在上述的例子中我們已經知道如果我們可以「塞」東西進去這個 List ,
那就不可能做到「List<Cat> 是 List<Animal> 的 subtype」。
所以我們要另外定一個 list 叫做 ConstList,意思是這個 list 是 constant,也就是我們不能改動裡面的值。
你可能會問:「既然這個 list 不能塞東西進去,那我要這個 list 幹麻?」,雖然不能塞東西進去,
但是你可以在產生這個 list 的時候就把值設定好,例如
ConstList<Cat> list = new ConstList<Cat>(cat1, cat2, cat3);
那麼,我們要怎麼告訴 typechecker 說「假設 A 是 B 的 subtype,那麼 ConstList<A> 就是 ConstList<B> 的 subtype」呢?
以下舉幾個程式語言當例子:
FlowType
(其實我本來想要用 TypeScript 說明的,結果一查資料才發現 TypeScript 在這塊實作其實是有問題的 issue 1394)
class ConstList<+T> { ... }
Kotlin
class ConstList<out T> { ... }Java
Java 無法在宣告 class 時宣告是否為 covariant,只能在宣告變數時使用。
ConstList<? extends Animal> list = new ConstList<Cat>()