给出这个示例代码:
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.
对我来说似乎是一个编译器错误...
答案 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。