Covariant(協變)與 Contravariant(逆變)


今天為了解釋某個問題而提到協變和逆變,發現每次解釋這兩個概念都會忘掉它們的本質,然后要重新看看定義,重新消化一下才能說明白。所以我決定把自己對協變和逆變的理解寫下來,以免將來再次忘掉。

我知道 .NET 的用戶喜歡用 delegate TResult Func<in T, out TResult>(T arg); 來解釋協變逆變,我則喜歡把 Func 的簽名簡寫為 Haskell 簽名形式。也就是說,把 Func<T, TResult> 寫成 f :: a -> b 的形式;把 Func<T1, T2, Result> 寫成 f :: a -> b -> c 的形式。

其實無論是協變還是逆變,本質都是一樣的:對於簽名為 f :: A -> B 的函數,實際可接受的參數范圍為 ASub,實際可返回的參數范圍為 BSub。這個很容易理解吧?任何時候子類的實例都可以當做超類實例來使用,無論是接受還是返回。

協變和逆變用於描述高階函數簽名,如 f :: (X -> Y) -> Z。那上面的 f :: A -> B 做模版,我們可以把 (X -> Y) 看做 A,把 Z 看做 B。應用同樣的邏輯,函數實際可接受的參數范圍是 (X -> Y) 的子類,實際可返回的參數范圍是 Z 的子類。對於后者我們沒什么疑問,但 (X -> Y) 的子類到底是什么呢?它的所謂「子類」應該是 (XSuper -> YSub)

為什么說 (X -> Y) 的「子類」應該是 (XSuper -> YSub) 呢?因為子類在能力上應該完整覆蓋超類的能力,因此如果對方要求你提供一個函數,這個函數接受 X 類型返回 Y 類型,你提供的函數至少要能接受 X 的超類而返回必須是 Y 的子類。這時候 X 是逆變參數(類型可以更寬松),而 Y 是協變參數(類型可以更嚴格)。

一般來說,如果把「類型可以更嚴格」看做協變的話,函數的返回類型一定可以協變,非高階函數的參數也可以協變,高階函數的非函數參數同樣可以協變。把「類型可以更寬松」看做逆變的話,只有高階函數中的函數參數中會出現逆變,也就是作為參數的參數出現。那么參數的參數的參數呢?也就是說高階函數的參數仍然是高階函數,那會怎么樣呢?這個大家可以嘗試自行分析,盯住 f :: ((X -> Y) -> Z) -> W 看一會兒,再不停類比上文的 f :: A -> B,或許你就明白了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM