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类型的术语的上下文中安全使用。 CatAnimal子类型 ,而AnimalCat超类型

当您只处理一堂课时,这很容易。 但是如何在以下情况下工作:

  • Array — [Cat][Animal]的子类型吗?
  • 通用PetOwnerPetOwner的子类型吗?
  • 闭包(Cat) -> Void(Animal) -> Void的子类型吗?

第一个是直观的; 是。 第二个是“否”(此后)。

最后一个是不,令人惊讶,事实恰恰相反。 (Animal) -> Void (Cat) -> Void 的子类型

事实证明这不是黑魔法。 这是您要记住的一种(合理的)编程语言选择。 这些选择称为协方差 方差

仔细观察为什么[Cat]将成为[Animal]的子类型。

首先,从Cat开始是Animal的子类型,写为:

Cat → Animal

(让我们将此称为子类型化方向。)

经过深思熟虑, [Animal]可以握住CatAnimal是有意义的。 因此,作为一个伟大的设计师,我只是认为[Cat][Animal]的子类型方向应与Cat → Animal ,我们得到:

[Cat] → [Animal]

现在[Cat][Animal]的子类型。 使用与原始类型相同的子类型化方向的决定称为协方差。

协方差的另一个示例是闭包的返回类型

 让intBuilder:()-> Int = { 
返回5
}让anyBuilder:()-> Any = intBuilder ___ OK

如您所见, IntAny的子类型,并且() -> 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放入intHandleranyHandler可以处理 任何东西, 包括 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′的子类型。

好吧,这很容易。 IntString不变的,因为它们是类型不兼容的。 一个不能用作另一个。

Swift的泛型是不变性 ,这意味着PetOwner不是PetOwner的子类型。 他们彼此无关。


让我们结束一些陷阱。 为什么标准库中的泛型(例如Array协变量)却我们自己定义的泛型类型(例如PetOwner )是不变的?

好吧,在这种情况下,似乎背后确实有魔术。 里克说:

“ Swift泛型通常是不变的,但是Swift标准库集合类型(即使这些类型看起来是常规的泛型类型)使用了凡人无法访问的某种魔术,使它们成为协变。”

参考: https : //www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html