协议不符合自己?
为什么这个Swift代码不能编译?
protocol P { } struct S: P { } let arr:[P] = [ S() ] extension Array where Element : P { func test<T>() -> [T] { return [] } } let result : [S] = arr.test()
编译器说:“typesP
不符合协议P
”(或在更高版本的Swift中,“使用'P'作为符合协议'P'的具体types不被支持”)。
为什么不? 不知怎的,这感觉就像是一个语言漏洞。 我意识到这个问题源于将数组arr
声明为一个协议types的数组,但这是不合理的事情吗? 我认为协议在那里确实有助于像types层次结构一样提供结构?
编辑:工作瓦特/斯威夫特,另一个主要版本(提供了一个新的诊断)18个多月,从@AyBayBay评论让我想重写这个答案。 新的诊断是:
不支持“P”作为符合协议“P”的具体types。“
这实际上使整个事情更清晰。 这个扩展:
extension Array where Element : P {
当Element == P
不适用,因为P
不被视为P
的具体一致性。 (下面的“把它放在一个盒子里”解决scheme仍然是最通用的解决scheme。)
老答案:
这是又一个元types的例子。 Swift 真的希望你能够为大多数不重要的东西获得具体的types。 (我不认为这是真的,你完全可以创造出大小为[P]
不是一个具体的types(你不能为P
分配一个已知大小的内存块)。P
东西,因为它是通过间接的方式完成的 )。我不认为有证据表明这是“不应该”的情况。 这看起来很像他们的“不工作”案件之一。 (不幸的是,让Apple确认这些情况之间的区别几乎是不可能的。) Array<P>
可以是一个variablestypes( Array
不能),这表明他们已经在这个方向上做了一些工作,但是Swift元types有很多尖锐的边缘和未实现的情况。 我不认为你会得到比这更好的“为什么”的答案。 “因为编译器不允许它。” (不满意,我知道,我的整个Swift生活…)
解决scheme几乎总是把东西放在一个盒子里。 我们build立一个types橡皮擦。
protocol P { } struct S: P { } struct AnyPArray { var array: [P] init(_ array:[P]) { self.array = array } } extension AnyPArray { func test<T>() -> [T] { return [] } } let arr = AnyPArray([S()]) let result: [S] = arr.test()
当Swift允许您直接执行此操作(我最终期望这样做)时,它可能会自动为您创build此框。 recursion枚举就是这样的历史。 你不得不把它们装箱,这是令人难以置信的烦人和限制,然后最后编译器添加indirect
以更自动indirect
做同样的事情。
如果扩展了CollectionType
协议而不是协议的Array
和约束作为具体types,则可以按照以下方式重写以前的代码。
protocol P { } struct S: P { } let arr:[P] = [ S() ] extension CollectionType where Generator.Element == P { func test<T>() -> [T] { return [] } } let result : [S] = arr.test()
为什么协议不符合自己?
在一般情况下允许协议符合自己是不合适的。 问题在于静态协议要求。
这些包括:
-
static
方法和属性 - Initialisers
- 相关types(尽pipe这些types目前阻止使用协议作为实际types)
我们可以在通用占位符T
上访问这些需求,其中T : P
– 但是我们不能在协议types本身上访问它们,因为没有具体的符合types转发。 所以我们不能让T
成为P
考虑如果我们允许Array
扩展适用于[P]
,下面的例子会发生什么:
protocol P { init() } struct S : P {} struct S1 : P {} extension Array where Element : P { mutating func appendNew() { // if Element is P, we cannot possibly construct a new instance of it, // as you cannot construct an instance of a protocol. append(Element()) } } var arr: [P] = [ S(), S1() ] // Using 'P' as a concrete type conforming to protocol 'P' is not supported arr.appendNew()
我们不可能在[P]
上调用appendNew()
,因为P
( Element
)不是一个具体的types,因此不能被实例化。 它必须在具有混凝土types的元素的数组上调用,其types符合P
这是一个类似的故事与静态方法和财产要求:
protocol P { static func foo() static var bar: Int { get } } struct SomeGeneric<T : P> { func baz() { // if T is P, what's the value of bar? // there isn't one – because there's no implementation // of bar's getter defined on P itself. print(T.bar) T.foo() // if T is P, what method are we calling here? } } // Using 'P' as a concrete type conforming to protocol 'P' is not supported SomeGeneric<P>().baz()
我们不能用SomeGeneric<P>
来说话。 我们需要静态协议要求的具体实现(注意在上面的例子中没有定义foo()
或bar
实现)。 虽然我们可以在P
扩展中定义这些需求的实现,但是这些只是针对符合P
的具体types定义的 – 您仍然不能在P
本身上调用它们。
正因为如此,Swift完全不允许我们使用协议作为符合自身的types,因为当协议有静态需求时,它不会。
实例协议要求没有问题,因为您必须在符合协议的实际实例上调用它们(因此必须实现这些要求)。 因此,当对一个types为P
的实例调用一个需求时,我们可以将该调用转发到该需求的基础具体types的实现上。
然而,在这种情况下对规则做出特殊的例外可能会导致在通用代码如何处理协议时令人惊讶的不一致。 尽pipe如此,这种情况与associatedtype
要求并不是很不相似,它们(当前)阻止您使用协议作为types。 有一个限制,防止你使用协议作为一种符合自己的types时,它有静态要求可能是未来版本的语言的选项
编辑:正如下面的探讨,这看起来像什么Swift团队的目标。
@objc
协议
实际上,这正是语言对待@objc
协议的方式。 当他们没有静态要求时,他们自己就符合了。
以下编译就好了:
import Foundation @objc protocol P { func foo() } class C : P { func foo() { print("C's foo called!") } } func baz<T : P>(_ t: T) { t.foo() } let c: P = C() baz(c)
baz
要求T
符合P
; 但是我们可以用P
代替T
因为P
没有静态的要求。 如果我们为P
添加一个静态的需求,这个例子不再编译:
import Foundation @objc protocol P { static func bar() func foo() } class C : P { static func bar() { print("C's bar called") } func foo() { print("C's foo called!") } } func baz<T : P>(_ t: T) { t.foo() } let c: P = C() baz(c) // Cannot invoke 'baz' with an argument list of type '(P)'
所以解决这个问题的一个办法是让你的协议@objc
。 当然,在许多情况下,这不是一个理想的解决方法,因为它会迫使符合types成为类,并且需要Obj-C运行时,因此不能在非Apple平台(如Linux)上运行。
但是我怀疑这个限制是(语言)为什么语言已经为@objc
协议实现了“没有静态需求的协议符合自己”的@objc
。 通用代码可以被编译器显着简化。 为什么? 因为它知道它总是处理引用。 T
的布局在编译时已知; 它不是一个不透明的布局,直到运行时才知道。
因此,由于这个简化,我猜测它使得Swift团队能够(相对)容易地实现。 这是纯粹的猜测。
然而,这个特性是有意的,希望能够被推广到非@objc
协议,正如Swift团队成员Slava Pestov 在SR-55的回复中对你的查询( 这个问题的提示)的回应所证实的那样:
Matt Neuburg添加了评论 – 2017年9月7日1:33 PM
这确实编译:
@objc protocol P {} class C: P {} func process<T: P>(item: T) -> T { return item } func f(image: P) { let processed: P = process(item:image) }
添加
@objc
使其编译; 删除它使它不能再次编译。 Stack Overflow中的一些人认为这是令人惊讶的,并想知道这是故意还是错误的边缘情况。Slava Pestov添加了评论 – 2017年9月7日1:53 PM
这是故意的 – 解决这个限制是这个错误是关于。 就像我说的那么棘手,我们还没有具体的计划。
所以希望有一天语言也会支持非@objc
协议。
但是,对于非@objc
协议,目前有哪些解决scheme?
使用协议约束实现扩展
在Swift 3.1中,如果你想要一个具有给定通用占位符或关联types必须是给定协议types的约束的扩展(不仅仅是符合该协议的具体types),你可以简单地用一个==
约束来定义它。
例如,我们可以写你的数组扩展名为:
extension Array where Element == P { func test<T>() -> [T] { return [] } } let arr: [P] = [ S() ] let result: [S] = arr.test()
当然,这现在阻止我们使用符合P
具体types元素对数组进行调用。 我们可以通过为Element : P
定义一个额外的扩展来解决这个问题,并且直接转向== P
扩展:
extension Array where Element : P { func test<T>() -> [T] { return (self as [P]).test() } } let arr = [ S() ] let result: [S] = arr.test()
但值得注意的是,这将执行数组的O(n)转换为[P]
,因为每个元素都必须装入存储容器中。 如果性能问题,您可以通过重新实现扩展方法来解决这个问题。 这不是一个完全令人满意的解决scheme – 希望未来版本的语言将包括expression“协议types或符合协议types”约束的方式。
在Swift 3.1之前,实现这一点的最普通的方法, 就像Rob在他的回答中所显示的那样 ,就是简单地为[P]
build立一个包装types,然后你可以定义你的扩展方法。
将协议types的实例传递给受约束的通用占位符
罗布说 ,解决这个问题的最普遍的办法是build立一个橡皮擦 。 这允许我们通过将实例需求转发到底层实例(就像我们之前描述的那样)来将协议types实例包装在符合该协议的具体types中。
例如,考虑以下(人为的,但相当普遍的)情况:
protocol P { var bar: Int { get set } func foo(str: String) } struct S : P { var bar: Int func foo(str: String) {/* ... */} } func takesConcreteP<T : P>(_ t: T) {/* ... */} let p: P = S(bar: 5) // Cannot invoke 'takesConcreteP' with an argument list of type '(P)' takesConcreteP(p)
我们不能把p
传给takesConcreteP(_:)
,因为它期望一个符合P
的具体types的实例,而不是types为P
本身的实例。
我们可以通过构build一个具体的盒子(一个橡皮擦)来将P
的实例需求转发到一个底层的任意实例上,该实例符合P
:
struct AnyP : P { private var base: P init(_ base: P) { self.base = base } var bar: Int { get { return base.bar } set { base.bar = newValue } } func foo(str: String) { base.foo(str: str) } }
现在我们可以用AnyP
而不是P
来说话:
let p = AnyP(S(bar: 5)) takesConcreteP(p)
现在,考虑一下为什么我们必须build立这个盒子。 正如我们早些时候讨论的那样,Swift 需要一个具体的types来处理协议有静态需求的情况。 考虑一下P
是否有静态需求 – 我们需要在AnyP
实现。 但是,它应该如何实施呢? 我们正在处理符合P
任意实例 – 我们不知道它们的底层具体types是如何实现静态需求的,因此我们无法在AnyP
有意义地expression这AnyP
。
因此,这种情况下的解决scheme在实例协议要求的情况下才真正有用。 在一般情况下,我们仍然不能把P
看作符合P
的具体types。