【问题标题】:Why does Swift not allow a type conforming to a protocol to be used in an argument taking the protocol?为什么 Swift 不允许在接受协议的参数中使用符合协议的类型?
【发布时间】:2017-11-17 09:10:18
【问题描述】:

鉴于此示例代码:

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) -> Void”类型的值转换为预期的参数类型 ‘(P) -> 虚空’。

为什么?

注意,在我看来它不应该给出这个错误,因为类型约束&lt;Z: P&gt; 要求 Z 绝对必须符合协议 P。所以,绝对没有理由convert 从 Z 到 P,因为 Z 已经符合 P。

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

【问题讨论】:

  • 这段代码背后的想法是什么?
  • 编译器是正确的——(Z) -&gt; Void 不是(P) -&gt; Void。为了说明为什么会这样,让我们​​说String : P 和Int : P。现在让我们用Int 替换Z。我们不能将(Int) -&gt; Void 传递给(P) -&gt; Void。为什么?那么(P) -&gt; Void 接受符合P 的任何东西——例如,我们可以传入String。但如果该函数实际上是 (Int) -&gt; Void,我们会尝试将 String 传递给 Int,这显然是不合理的。
  • 我不同意。因为我们在运行时对 Z 的唯一了解就是它是一个 P。它是否真的是一个 Int 或 String 完全无关紧要。
  • @CommaToast 否,在 runtime Z 是用具体类型实现的。它是P 的子类型,但P 不是它的子类型,就像在我上面的示例中String 是P 的子类型一样,但P 不是@987654344 的子类型@.

标签: swift generics closures


【解决方案1】:

编译器是正确的——(Z) -&gt; Void 不是(P) -&gt; 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) -&gt; Void 传递给(P) -&gt; Void。为什么?那么(P) -&gt; Void 接受任何符合P 的东西——例如,我们可以传入String。但是我们传递给j 的函数实际上是一个(Int) -&gt; 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) -&gt; Void 是一个函数可以处理 any P 符合参数。然而(Z) -&gt; Void 是一个函数,它只能处理一个特定 符合P 的具体类型参数(例如我们上面的例子中的Int)。将其键入为可以处理 any P 符合参数的函数将是一个谎言。

用更专业的方式说,(Z) -&gt; Void 不是(P) -&gt; Void 的子类型。函数的参数类型为contravariant,这意味着(U) -&gt; Void 是
(V) -&gt; Void 的子类型当且仅当V 是U 的子类型。但是P 不是Z : P 的子类型——Z 是一个占位符,它在运行时被替换为符合P(也是其子类型)的具体类型。

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

【讨论】:

  • 如果 Z === Any

    那么我不同意;在这种情况下, (Z)-> Void 绝对应该可以作为 (P)-> Void 传递,因为我们对 Z 的唯一了解就是它的 P-ness!现在,在 Z 具有函数知道的其他约束的情况下,我同意它们不一样,因为 Any

    不仅仅是 P 并且函数可能需要 Q-ness 和 R-ness。

  • 所以这似乎是编译器强制 Any

    不能作为 P 传递的情况,只是因为 Any

    不能作为 P 传递。这是抛出用洗澡水把婴儿弄出来。

  • 尽管如此,由于您的回答解释了当前 Swift 编译器背后的(部分有缺陷的)基本原理,我将其标记为正确。
  • @CommaToast "在 Z === Any

    " 的情况下——这只是一种情况;泛型函数的实现在编译时不知道Z 是什么(但Z 无论如何不能是P,因为协议并不总是符合自己)。这同样是非法的:func foo&lt;T : P&gt;(_ x: T, _ p: P) { let y: T = p }。请记住,Swift 泛型不像 C++ 模板。编译器不会为通用占位符的每个替换创建单独的专门实现,它只发出一个实现(尽管它可以专门作为优化)。

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多