Swift 2.1 函數類型轉換:協變與逆變

做者:uraimo,原文連接,原文日期:2015-09-29
譯者:Lanford3_3;校對:shanks;定稿:Ceegit

這篇 Swift 2.1 相關的文章須要使用 Xcode 7.1 beta 或者更新的版本, 你能夠經過 GitHub 或者是 zip 文件 來獲取相關 playground 文件。github

在即將和 Xcode 7.1 一塊兒到來的 Swift 2.1 中(譯者注:原文發表於 2015 年 9 月=,=),函數類型將支持協變與逆變。讓咱們看看這意味着什麼。 swift

在計算機科學及類型推斷的語境中,型變(variance)這個詞表示的是,兩種類型之間的關係是如何影響他們派生出的複雜類型之間的關係的。複雜類型間的關係,根據原始類型間的關係來看,無外乎不變(invariance)、協變(covariance)與逆變(contravariance)。要高效地使用複雜類型,理解這種派生關係是如何定義的是很是重要的。安全

咱們用僞代碼來對此進行闡釋。考慮這樣一個複雜的參數類型 List<T> 和兩個簡單類型: CarCar 的一個子類型(subtype) Maserati函數

咱們把已有的兩種類型做爲 List<T>T 來得到兩種新類型,以後就能夠經過討論新類型的關係,來對不變,協變和逆變加以解釋:ui

  • 協變:若是 List<Maserati> 也是 List<Car> 的子類型,那麼原始類型間的關係也存在於 List 中,這是由於,List 和他的原始類型是協變的。翻譯

  • 逆變:但假若 List<Car>List<Maserati> 的子類型,那麼原始類型間的關係和 List 派生出的複雜類型間的關係是相反的,由於 List 相對於它的原始類型是逆變的。code

  • 不變List<Car> 不是 List<Maserati> 的子類型,反之亦然,則兩種複雜類型間沒有衍生關係。ip

每種語言都採用了一個特定的型變方法集,瞭解複雜類型間是如何相互聯繫的,有助於理解兩個複雜類型是否兼容,是否能在一些情境下互換,就像是一種類型和他的子類型那樣。get

在函數類型(Function Types)的語境中,複雜類型的兼容問題能夠歸結爲一個簡單的問題:當你須要使用 A 類型的函數時,在什麼狀況下使用 B 類型的函數進行替代是安全的?

一個通用的規則是,可以兼容的函數類型有這樣的特徵:其參數是更加泛用的父類型(相比於 A 函數所聲明的參數類型,A 的調用者也可以處理更特殊的參數),返回的結果則是一個更加特殊的子類型(A 的調用者會把返回值的類型當成 A 中聲明的父類型的簡化版)1,參數是逆變的,而返回值是協變的。

譯者注:若是 T1T2 的子類型,則能夠表示爲 T1 < T2,那麼上面的規則就能夠表示爲:對函數類型 F1 = S1 -> T1F2 = S2 -> T2 來講,當且僅當 S2 < S1T1 < T2 時,F1F2 的子類型。
S1S2 在入參位置上,他們之間的關係和 F1F2 間的關係是相反的,因此入參是逆變的,同時,T1T2 在出參位置上,他們之間的關係和 F1F2 間的關係是相同的,因此出參是協變的。

在 Swift 2.1 前的版本中,函數類型都是不變(invariance)的,若是你在 Playground 中嘗試運行下面的代碼,你會獲得一些相似這樣的警告:

// Cannot convert value of type '(Int) -> Int' to expected argument type '(Int) -> Any 
// (沒法把 '(Int) -> Int' 轉換爲指望的參數類型 '(Int) -> Any')
func testVariance(foo:(Int)->Any){foo(1)}

func innerAnyInt(p1:Any) -> Int{ return 1 }
func innerAnyAny(p1:Any) -> Any{ return 1 }
func innerIntInt(p1:Int) -> Int{ return 1 }
func innerIntAny(p1:Int) -> Any{ return 1 }

testVariance(innerIntAny)
testVariance(innerAnyInt)
testVariance(innerAnyAny)
testVariance(innerIntInt)

在 Swift 2.1 中狀況發生了改變,Swift 已經支持函數類型轉換,如今參數是逆變的,而返回值是協變的。

回到上面的示例代碼,即使 testVariance 函數輸入參數的類型是 Int -> Any,但如今傳入 Any -> AnyAny -> IntInt -> Int 三種類型的函數也都是容許的。

譯者注:上述這個 Int 和 Any 的例子其實並不合適,由於 Int 並非 Any 的子類型。能夠參考下面這個例子:

class Animal {}
class Cat: Animal {}

func innerAnimalCat(p1: Animal) -> Cat { return Cat() }
func innerAnimalAnimal(p1: Animal) -> Animal { return Cat() }
func innerCatCat(p1: Cat) -> Cat { return Cat() }
func innerCatAnimal(p1: Cat) -> Animal { return Cat() }

func testVariance(foo: (Cat) -> Animal) { foo(Cat()) }

testVariance(innerAnimalCat)
testVariance(innerAnimalAnimal)
testVariance(innerCatCat)
testVariance(innerCatAnimal)

說點什麼?來 Twitter 找我吧~

校者注:關於協變與逆變,還能夠參考翻譯組翻譯的另一篇文章,解釋的更加詳細:Friday Q&A 2015-11-20:協變與逆變


我並不太理解括號中的內容。對於這段話想表達的意思,舉個例子來講明應該是,定義類型 Animal 及其子類型 Cat,對於函數 test(catAnimalF: Cat -> Animal) 中的函數類型 A catAnimalF: Cat -> Animal 來講,是可使用函數 B animalCatF: Animal -> Cat 來替換的。由於 animalCatF 相較於 catAnimalF,其參數類型 Animal 是比 Cat 更加泛用的父類型,而其返回值類型則更加特殊。但做者在括號內的解釋我卻沒看懂。第一個括號是想說,用 B 替代 A 以後,相較於 A 所聲明的參數類型(Cat),A 的調用者(test)可以處理一個更加特殊的類型?感受不對誒...第二個括號意思是,在用 B 替代 A 後,其調用者(test)會把返回的類型(Cat)做爲 A 中聲明的返回類型(Animal)的簡化版處理?這個倒好像說的過去囧...(校者注:第一個括號的理解,A 的調用者,也就是函數 test,函數類型的入參能夠是 Cat 的父類,也就是 Animal,譯者理解是對的,第二個括號理解也是對的。)(定稿注:正好翻譯了以前那篇「協變與逆變」,因此譯者的理解是正確的。)

本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg


  1. 1
相關文章
相關標籤/搜索