议定书不符合自己的要求?

时间:2015-10-13 21:01:59

标签: swift generics

为什么这个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()

编译器说:“类型P不符合协议P”(或者,在Swift的更高版本中,“使用'P'作为符合协议'P'的具体类型是不支持。“)。

为什么不呢?不知怎的,这感觉就像语言中的漏洞。我意识到问题源于将数组arr声明为协议类型的数组,但这是不合理的事情吗?我认为协议正是为了帮助提供类似层次结构的结构?

3 个答案:

答案 0 :(得分:62)

为什么不协议符合自己的要求?

在一般情况下允许协议符合自己是不合理的。问题在于静态协议要求。

这些包括:

  • static方法和属性
  • Initialisers
  • 关联类型(尽管这些类型目前阻止将协议用作实际类型)

我们可以在T的通用占位符T : P上访问这些要求 - 但是我们无法在协议类型本身上访问它们,因为没有具体的符合类型转发。因此,我们不允许TP

如果我们允许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()]

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
arr.appendNew()

我们无法在appendNew()上调用[P],因为PElement)不是具体类型,因此无法实例化。 必须在具有具体类型元素的数组上调用,其中该类型符合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?
  }
}

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
SomeGeneric<P>().baz()

我们无法谈论SomeGeneric<P>。我们需要静态协议要求的具体实现(注意上面示例中定义的foo()bar no 实现方式)。虽然我们可以在P扩展中定义这些要求的实现,但这些要求仅针对符合P的具体类型定义 - 您仍然无法在P本身上调用它们。

正因为如此,Swift完全不允许我们将协议用作符合其自身的类型 - 因为当该协议具有静态要求时,它就不会。

实例协议要求没有问题,因为必须在符合协议的实际实例上调用它们(因此必须已实现要求)。因此,当在类型为P的实例上调用需求时,我们可以将该调用转发到该需求的基础具体类型的实现。

但是,在这种情况下对规则进行特殊例外可能会导致通用代码处理协议的方式出现意外的不一致。虽然如此,但情况与associatedtype要求不太相似 - 这(当前)阻止您将协议用作类型。有一个限制,阻止您将协议用作符合自身的类型,当它具有静态要求时,可以选择该语言的未来版本

编辑如下所述,这看起来就像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) // error: Cannot invoke 'baz' with an argument list of type '(P)'

因此解决此问题的一个方法是制定协议@objc。当然,在许多情况下,这不是理想的解决方法,因为它会强制您的符合类型成为类,并且需要Obj-C运行时,因此不能使其在非Apple平台(例如Linux)上可行。 / p>

但是我怀疑这种限制是(一种)语言已经实现的主要原因,没有静态要求的协议符合自身的要求。用于@objc协议。编译器可以大大简化围绕它们编写的通用代码。

为什么呢?因为@objc协议类型的值实际上只是使用objc_msgSend调度其需求的类引用。另一方面,非@objc协议类型的值更复杂,因为它们同时携带值和见证表,以便管理其(可能是间接存储的)包装值的内存并确定哪些实现分别要求不同的要求。

由于@objc协议的这种简化表示,这种协议类型P的值可以共享与通用值相同的内存表示&#39;类型为通用占位符T : P可能使Swift团队轻松实现自我一致性。对于非@objc协议,情况并非如此,因为这些通用值目前不包含值或协议见证表。

然而,此功能 是有意的,并且有望推广到非@objc协议,正如Swift团队成员Slava Pestov in the comments of SR-55在回复您的查询时所确认的那样关于它(由this question提示):

  

Matt Neuburg发表评论 - 2017年9月7日下午1:33

     

这会编译:

@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

     

它是故意的 - 解除这个限制是这个bug的意思。   就像我说的那样棘手,我们还没有任何具体的计划。

所以希望有一天语言能够支持非@objc协议。

但非@objc协议的当前解决方案是什么?

使用协议约束实现扩展

在Swift 3.1中,如果你想要一个带有约束的扩展,给定的通用占位符或相关类型必须是给定的协议类型(而不仅仅是符合该协议的具体类型) - 你可以简单地用{定义} {1}}约束。

例如,我们可以将您的数组扩展名编写为:

==

当然,这现在阻止我们在具有符合extension Array where Element == P { func test<T>() -> [T] { return [] } } let arr: [P] = [S()] let result: [S] = arr.test() 的具体类型元素的数组上调用它。我们可以通过为P定义一个额外的扩展来解决这个问题,然后转发到Element : P扩展名:

== P

然而,值得注意的是,这会将数组执行O(n)转换为extension Array where Element : P { func test<T>() -> [T] { return (self as [P]).test() } } let arr = [S()] let result: [S] = arr.test() ,因为每个元素都必须在存在容器中装箱。如果性能是一个问题,您可以通过重新实现扩展方法来解决这个问题。这不是完全满意的解决方案 - 希望该语言的未来版本将包含一种表达协议类型符合协议类型的方法#39;约束

在Swift 3.1之前,实现这一目标的最常用方法是as Rob shows in his answer,只需为[P]构建一个包装器类型,然后就可以在其上定义扩展方法了。

将协议类型的实例传递给受约束的通用占位符

考虑以下(人为的,但并非罕见的)情况:

[P]

我们无法将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) // error: Cannot invoke 'takesConcreteP' with an argument list of type '(P)' takesConcreteP(p) 传递给p,因为我们目前无法将takesConcreteP(_:)替换为通用占位符P。让我们来看看我们可以通过几种方式解决这个问题。

1。打开存在感

不是试图用T : P代替P,而是如果我们可以深入研究T : P类型值包装的基础具体类型并替代它?不幸的是,这需要一种名为opening existentials的语言功能,目前该功能无法直接供用户使用。

但是,当访问它们上的成员时,Swift 隐式打开存在(协议类型值)(即它挖掘出运行时类型并使其以通用占位符的形式访问)。我们可以在P上的协议扩展中利用这一事实:

P

请注意扩展方法采用的隐式通用extension P { func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) { takesConcreteP(self) } } 占位符,用于键入隐式Self参数 - 这会在所有协议扩展成员的幕后发生。当在协议类型值self上调用这样的方法时,Swift挖掘出底层的具体类型,并使用它来满足P通用占位符。这就是我们能够使用Self致电takesConcreteP(_:)的原因 - 我们用self来满足T

这意味着我们现在可以说:

Self

p.callTakesConcreteP() 被调用,其通用占位符takesConcreteP(_:)被底层具体类型(在本例中为T)所满足。请注意,这不是符合他们自己的协议,因为我们要替换具体类型而不是S - 尝试向协议添加静态要求并查看发生的情况当你从P内召唤它时。

如果Swift继续禁止协议符合自己,那么下一个最好的替代方案是在尝试将它们作为参数传递给泛型类型的参数时隐式打开存在 - 有效地完成我们的协议扩展蹦床所做的事情,只是没有样板

但请注意,开放存在不是解决不符合自身协议问题的一般解决方案。它没有处理协议类型值的异构集合,它们可能都有不同的底层具体类型。例如,考虑:

takesConcreteP(_:)

出于同样的原因,具有多个struct Q : P { var bar: Int func foo(str: String) {} } // The placeholder `T` must be satisfied by a single type func takesConcreteArrayOfP<T : P>(_ t: [T]) {} // ...but an array of `P` could have elements of different underlying concrete types. let array: [P] = [S(bar: 1), Q(bar: 2)] // So there's no sensible concrete type we can substitute for `T`. takesConcreteArrayOfP(array) 参数的函数也会有问题,因为参数必须采用相同类型的参数 - 但是如果我们有两个T值,那么&#39;我们无法在编译时保证它们都具有相同的基础具体类型。

为了解决这个问题,我们可以使用类型橡皮擦。

2。构建类型橡皮擦

作为Rob saystype eraser是解决不符合自身协议问题的最通用解决方案。它们允许我们将协议类型的实例包装在符合该协议的具体类型中,方法是将实例需求转发给底层实例。

因此,让我们构建一个类型擦除框,将P的实例要求转发到符合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

现在,考虑一下为什么我们必须建造那个盒子。正如我们早期讨论的那样,对于协议具有静态要求的情况,Swift需要具体类型。考虑let p = AnyP(S(bar: 5)) takesConcreteP(p) // example from #1... let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))] takesConcreteArrayOfP(array) 是否有静态要求 - 我们需要在P中实现它。但它应该被实施为什么?我们在这里处理符合AnyP的任意实例 - 我们不知道它们的基本具体类型如何实现静态需求,因此我们无法在P中有意义地表达这一点。

因此,在这种情况下,解决方案仅在实例协议要求的情况下才真正有用。在一般情况下,我们仍然无法将AnyP视为符合P的具体类型。

答案 1 :(得分:54)

编辑:与Swift一起工作18个月,另一个主要版本(提供新的诊断),@ AyBayBay的评论让我想重写这个答案。新的诊断是:

  

&#34;使用&#39; P&#39;作为符合协议的具体类型&#39; P&#39;不受支持。&#34;

这实际上使整个事情变得更加清晰。这个扩展名:

extension Array where Element : P {

Element == P时不适用,因为P不被视为P的具体一致性。 (&#34;把它放在一个盒子里&#34;下面的解决方案仍然是最通用的解决方案。)

旧答案:

这是另一种元类型的情况。 Swift 真的希望你能找到适合大多数非平凡事物的具体类型。 [P]不是具体类型(您无法为P分配已知大小的内存块。)(我不认为&#39 ; s实际上是真的;你绝对可以创建大小P的东西,因为it's done via indirection。)我不认为有任何证据表明这是&#34;应该& #39; T&#34;工作。这看起来非常类似于他们之一尚未开始工作的#34;案例。 (不幸的是,让Apple确认这些案例之间的差异几乎是不可能的。)Array<P>可以是变量类型(Array不能)的事实表明他们已经已经在这方面做了一些工作,但Swift元型有很多锋利的边缘和未实现的情况。我不认为你会变得更好&#34;为什么&#34;答案不止于此。 &#34;因为编译器不允许它。&#34; (不知道,我知道。我整个斯威夫特的生活......)

解决方案几乎总是把东西放在一个盒子里。我们建造了一个类型橡皮擦。

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允许您直接执行此操作(我最终期望)时,可能只是为您自动创建此框。递归枚举正是这段历史。你必须打包它们,这非常烦人和限制,然后最后编译器添加了indirect来更自动地做同样的事情。

答案 2 :(得分:15)

如果扩展CollectionType协议而不是Array并将协议约束作为具体类型,则可以按如下方式重写以前的代码。

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()