为什么Swift不允许在参与协议的参数中使用符合协议的类型?

时间:2017-11-14 02:38:08

标签: swift generics closures

给出这个示例代码:

private protocol P {}
final private class X {
    private func j(j: (P) -> Void) -> Void {}
    private func jj<Z: P>(jj: (Z) -> Void) -> Void {
        j(j: jj)
    }
}

XCode 9.1中的Swift 4在行j(j: jj)上给出了这个编译器错误:

  

无法转换类型'(Z) - &gt;的值Void'到预期的参数类型   '(P) - &gt;无效”。

为什么呢?

注意,在我看来它不应该给出这个错误,因为类型约束<Z: P> 需要 Z绝对必须符合协议P.所以,应该绝对没有从Z转换为P的原因,因为Z已经符合P.

对我来说似乎是一个编译器错误...

1 个答案:

答案 0 :(得分:1)

编译器是正确的 - (Z) -> Void不是(P) -> Void。为了说明原因,请定义以下一致性:

extension String : P {}
extension Int : P {}

现在让Int代替Z

final private class X {

  func j(j: (P) -> Void) {
    j("foob")
  }

  func jj(jj: (Int) -> Void) {
    // error: Cannot convert value of type '(Int) -> Void' to expected argument
    // type '(P) -> Void'
    j(j: jj)
  }
}

我们无法将(Int) -> Void传递给(P) -> Void。为什么?好的(P) -> Void接受任何符合P的内容 - 例如,我们可以传入String。但我们传递给j的功能实际上是(Int) -> Void,因此我们尝试将String传递给Int参数,显然是不健全的。

如果我们重新使用泛型,那么为什么这不起作用仍应该相当清楚:

final private class X {

  func j(j: (P) -> Void) {
    j("foob")
  }

  func jj<Z : P>(jj: (Z) -> Void) {
    // error: Cannot convert value of type '(Z) -> Void' to expected argument
    // type '(P) -> Void'
    j(j: jj)
  }
}

X().jj { (i: Int) in
  print(i) // What are we printing here? A String gets passed in the above implementation..
}

(P) -> Void是一个函数可以处理符合任何 P的参数。但是(Z) -> Void是一个只能处理一个符合P特定具体类型参数的函数(例如上面例子中的Int)。将它键入一个可以处理任何 P符合性参数的函数将是一个谎言。

以更加技术性的方式,(Z) -> Void不是(P) -> Void的子类型。函数的参数类型为contravariant,这意味着(U) -> Void(V) -> Void的子类型,当且仅当V是{{1}的子类型时}}。但U不是P的子类型 - Z : P是一个占位符,它在运行时被替换为符合的具体类型(因此是一个子类型) )Z

当我们考虑相反的情况时,会出现更有趣的部分;也就是说,将P传递给(P) -> Void。虽然占位符(Z) -> Void只能通过Z : P的具体子类型来满足,但我们无法将P替换为P,因为protocols don't conform to themselves