Swift 2.1 函数类型转换:协变与逆变

作者:uraimo,原文链接,原文日期:2015-09-29
译者:Lanford3_3;校对:shanks;定稿:Cee

这篇 Swift 2.1 相关的文章需要使用 Xcode 7.1 beta 或者更新的版本, 你可以通过 GitHub 或者是 zip 文件 来获取相关 playground 文件。

在即将和 Xcode 7.1 一起到来的 Swift 2.1 中(译者注:原文发表于 2015 年 9 月=,=),函数类型将支持协变与逆变。让我们看看这意味着什么。

在计算机科学及类型推断的语境中,型变(variance)这个词表示的是,两种类型之间的关系是如何影响他们派生出的复杂类型之间的关系的。复杂类型间的关系,根据原始类型间的关系来看,无外乎不变(invariance)、协变(covariance)与逆变(contravariance)。要高效地使用复杂类型,理解这种派生关系是如何定义的是非常重要的。

我们用伪代码来对此进行阐释。考虑这样一个复杂的参数类型 List<T> 和两个简单类型: CarCar 的一个子类型(subtype) Maserati

我们把已有的两种类型作为 List<T>T 来获得两种新类型,之后就可以通过讨论新类型的关系,来对不变,协变和逆变加以解释:

  • 协变:如果 List<Maserati> 也是 List<Car> 的子类型,那么原始类型间的关系也存在于 List 中,这是因为,List 和他的原始类型是协变的。
  • 逆变:但倘若 List<Car>List<Maserati> 的子类型,那么原始类型间的关系和 List 派生出的复杂类型间的关系是相反的,因为 List 相对于它的原始类型是逆变的。
  • 不变List<Car> 不是 List<Maserati> 的子类型,反之亦然,则两种复杂类型间没有衍生关系。

每种语言都采用了一个特定的型变方法集,了解复杂类型间是如何相互联系的,有助于理解两个复杂类型是否兼容,是否能在一些情境下互换,就像是一种类型和他的子类型那样。

在函数类型(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:协变与逆变


[1]: 我并不太理解括号中的内容。对于这段话想表达的意思,举个例子来说明应该是,定义类型 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容