Swift中的协方差和协方差
首先,让我们检查一下是否需要这篇文章:
这不起作用:
var intHandler:(Int)-> Void = {(num)in
打印(数字)
}让anyHandler :(任何)-> Void = intHandler ___ 错误!
但相反的工作:
让anyHandler:(Any)-> Void = {(any)in
打印(任何)
} let intHandler:(Int)-> Void = anyHandler ___ OK。
最后,这有效:
let intResolverLater: ((Int) -> Void) -> Void = { f in
f(0)
}
var anyResolverLater: ((Any) -> Void) -> Void = intResolver ___ OK.
如果您已经知道会发生什么,则不需要本文。 但是,如果您好奇,请继续阅读!
您已经知道子类可以在使用其父级的任何地方使用的规则:
类Animal {...}类Cat:动物{...} let动物:Animal = Cat()
此行为称为子类型化。 更具规格的Cat
可以放入较大的Animal
容器中。
在技术术语中, T
的子类型可以在期望T
类型的术语的上下文中安全使用。 Cat
是Animal
的子类型 ,而Animal
是Cat
的超类型 。
当您只处理一堂课时,这很容易。 但是如何在以下情况下工作:
- Array —
[Cat]
是[Animal]
的子类型吗? - 通用 —
PetOwner
是PetOwner
的子类型吗? - 闭包 –
(Cat) -> Void
是(Animal) -> Void
的子类型吗?
第一个是直观的; 是。 第二个是“否”(此后)。
最后一个是不,令人惊讶,事实恰恰相反。 (Animal) -> Void
是 (Cat) -> Void
的子类型 !
事实证明这不是黑魔法。 这是您要记住的一种(合理的)编程语言选择。 这些选择称为协方差和逆 方差 。
仔细观察为什么[Cat]
将成为[Animal]
的子类型。
首先,从Cat
开始是Animal
的子类型,写为:
Cat → Animal
。
(让我们将此称为子类型化方向。)
经过深思熟虑, [Animal]
可以握住Cat
或Animal
是有意义的。 因此,作为一个伟大的设计师,我只是认为[Cat]
和[Animal]
的子类型方向应与Cat → Animal
,我们得到:
[Cat] → [Animal]
。
现在[Cat]
是[Animal]
的子类型。 使用与原始类型相同的子类型化方向的决定称为协方差。
协方差的另一个示例是闭包的返回类型 :
让intBuilder:()-> Int = {
返回5
}让anyBuilder:()-> Any = intBuilder ___ OK
如您所见, Int
是Any
的子类型,并且() -> Int
也是() -> Any
的子类型。 因此,我们可以说类似“ Swift中闭包的返回类型为协方差”。
这是决定朝原始类型的相反子类型方向进行的决定。 但是为什么我们要这么做呢?
好吧,事实证明,如果 闭包的参数 。 假设以下代码有效:
let intHandler:(Int)-> Void = {num in
打印(数字)
}让anyHandler :(任何)-> Void = intHandler ___ 编译错误!
当我们这样做时会发生什么?:
anyHandler(“某些字符串”)
intHandler
不希望接收String
(或Int
任何其他String
),因为它不知道如何处理它。 Swift不应该让我们编译。
现在考虑相反的情况:
让anyHandler:(Any)-> Void = {(any)in
打印(任何)
} let intHandler:(Int)-> Void = anyHandler ___ OK。
这是有道理的,为什么呢?
我们只能将Int
放入intHandler
, anyHandler
可以处理 任何东西, 包括 Int
。 因此, anyHandler
应该是anyHandler
的有效子类型。 这意味着(Any) -> Void
可以适合(Int) -> Void
!
闭包的最终子类型方向与原始参数的子类型方向相反的方式称为逆方差。
即,为什么下面的代码起作用:
let intResolverLater: ((Int) -> Void) -> Void = { f in
f(0)
}
var anyResolverLater: ((Any) -> Void) -> Void = intResolver ___ OK.
让我们逐步建立直觉。
1.首先,我们熟悉的变量(省略变量名):
让Any = Int
2.然后,对于一个函数, 左侧的参数( Int
)应该是右侧的参数的子类型( Any
) :
let(Int)-> Void = (Any)-> Void
尽管不是很明显,但这也指出(Any) -> Void
是(Int) -> Void
的子类型 。
3.最后,使用相同的逻辑, 左侧的参数( (Any) -> Void
)应该是右侧的参数( (Int) -> Void
) 的子类型 ,这已经由2.证明。 :
let( (任何)->虚空 )->虚空=((Int)->虚空)->虚空
例如,它会不断地交换订单!
–
从使用角度来看,请尝试查看这是有效的代码:
let intResolverLater:((Int)-> Void)-> Void = {(f)在
//使用f处理一些Int
f(1000)
}让anyResolverLater:( (任何)-> Void )-> Void = intResolverLater // anyResolver必须能够处理Any (can possibly be Int)
让anyResolver:(任何)->无效= {(任何)在
切换任何{
case num为Int:
print(“得到一个整数!\(num)”) ...处理其他情况 }
} // anyResolver可以在以后安全地处理Any(或Int)!
anyResolverLater( anyResolver )
您可以将闭包/函数f: (A) -> B
的子类型行为视为f: (A) -> B
作为水管,其进出水口与系统的其余部分配合,并保持水的流动:
要替换另一种没有泄漏(或安全 )的管道,新管道必须比初始管道具有更大的 出入路径 (A的超类) 或较小的出路 (B的子类型):
现在我们得到了一个新的管道f′
,该管道可以在预期初始管道f
任何地方都可以使用,反之亦然。 因此,闭合f′
是闭合f′
的子类型。
好吧,这很容易。 Int
和String
是不变的,因为它们是类型不兼容的。 一个不能用作另一个。
Swift的泛型是不变性 ,这意味着PetOwner
不是PetOwner
的子类型。 他们彼此无关。
让我们结束一些陷阱。 为什么标准库中的泛型(例如Array
协变量)却我们自己定义的泛型类型(例如PetOwner
)是不变的?
好吧,在这种情况下,似乎背后确实有魔术。 里克说:
“ Swift泛型通常是不变的,但是Swift标准库集合类型(即使这些类型看起来是常规的泛型类型)使用了凡人无法访问的某种魔术,使它们成为协变。”
参考: https : //www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html