为什么我们不能将类型转换为具有关联类型的协议类型,却可以使用泛型实现相同的效果? [英] Why can we not cast to protocol types with associated types but achieve the same effect using generics?

查看:105
本文介绍了为什么我们不能将类型转换为具有关联类型的协议类型,却可以使用泛型实现相同的效果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下代码:

extension Collection {
    func foo() -> Int {
        if self.first is Collection {
            return (self.first as! Collection).underestimatedCount // ERROR
        }
        else {
            return self.underestimatedCount
        }
    }
}

我们感到恐惧,而且显然令人费解:

协议集合"仅具有通用或相关类型要求,因此只能用作通用约束.

但是,这很容易编译:

func foo<C: Collection>(_ c: C) -> Int where C.Iterator.Element: Collection {
    if let first = c.first {
        return first.underestimatedCount // *
    } else {
        return c.underestimatedCount
    }
}

为什么?!

尤其是,编译器在*中不知道 是如何实现first的相关类型的;它只会得到它们曾经的 promise (因为任何类型为Collection has 的对象都可以实现它们).第一个示例中也有同样的保证!那么,为什么编译器抱怨一个而不是另一个?

我的问题是:在*行,编译器知道什么不在ERROR行?

解决方案

协议类型的值使用现有容器"表示(请参见在Youtube 上),它由固定大小的值缓冲区组成,以便存储值(如果值大小超过该值,它将堆分配),它是按顺序指向协议见证表的指针查找方法的实现以及指向值见证人表的指针,以管理值的生存期.

非专业的泛型使用几乎相同的格式(在本问答中,我在中对此进行了更深入的介绍) –调用它们时,将协议和值见证表的指针传递给函数,并且值本身使用值缓冲区存储在函数内部,该值缓冲区将为大于该缓冲区的值进行堆分配.

因此,由于实现方式的巨大相似性,我们可以得出这样的结论,即不能根据具有关联类型的协议或通用以外的Self约束进行交谈只是该语言的当前局限性.没有真正的技术原因无法实现,只是尚未实现.

以下是通用宣言"的摘录,内容为"广义存在论",讨论了它如何在实践中起作用:

存在类型的限制来自实现 限制,但允许使用协议类型的值是合理的 即使协议具有自我约束或相关类型.为了 例如,再次考虑IteratorProtocol以及如何将其用作 存在性:

protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

let it: IteratorProtocol = ...
it.next()   // if this is permitted, it could return an "Any?", i.e., the existential that wraps the actual element

此外,合理地限制关联的 存在的类型,例如"a Sequence,其元素类型为 String"可以通过将where子句放入 protocol<...>Any<...>(根据将protocol<...>重命名为Any<...>"):

let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]

前导.表示我们在谈论动态类型, 即符合Sequence协议的Self类型. 没有理由为什么我们不能在其中支持任意where子句 Any<...>.

从能够将值键入为具有关联类型的协议的角度来看,这只是很短的一步,可以将类型强制转换为给定类型,从而允许诸如第一个扩展名的内容进行编译.

Consider this code:

extension Collection {
    func foo() -> Int {
        if self.first is Collection {
            return (self.first as! Collection).underestimatedCount // ERROR
        }
        else {
            return self.underestimatedCount
        }
    }
}

We get the dreaded and apparently widely puzzling:

protocol 'Collection' can only be used as a generic constraint because it has Self or associated type requirements.

However, this happily compiles:

func foo<C: Collection>(_ c: C) -> Int where C.Iterator.Element: Collection {
    if let first = c.first {
        return first.underestimatedCount // *
    } else {
        return c.underestimatedCount
    }
}

Why?!

In particular, the compiler does not know in * how the associated types of (the type of) first have been realized; it only gets the promise that they have been (because any object of type Collection has to realize them). This same guarantee is there in the first example! So why does the compiler complain about one but not the other?

My question is: at line *, what does the compiler know that it does not in line ERROR?

解决方案

Protocol-typed values are represented using an 'existential container' (see this great WWDC talk on them; or on Youtube), which consists of a value-buffer of fixed size in order to store the value (if the value size exceeds this, it'll heap allocate), a pointer to the protocol witness table in order to lookup method implementations and a pointer to the value witness table in order to manage the lifetime of the value.

Unspecialised generics use pretty much the same format (I go into this in slightly more depth in this Q&A) – when they're called, pointers to the protocol and value witness tables are passed to the function, and the value itself is stored locally inside the function using a value-buffer, which will heap allocate for values larger than that buffer.

Therefore, because of the sheer similarity in how these are implemented, we can draw the conclusion that not being able to talk in terms of protocols with associated types or Self constraints outside of generics is just a current limitation of the language. There's no real technical reason why it's not possible, it just hasn't been implemented (yet).

Here's an excerpt from the Generics Manifesto on "Generalized existentials", which discusses how this could work in practice:

The restrictions on existential types came from an implementation limitation, but it is reasonable to allow a value of protocol type even when the protocol has Self constraints or associated types. For example, consider IteratorProtocol again and how it could be used as an existential:

protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

let it: IteratorProtocol = ...
it.next()   // if this is permitted, it could return an "Any?", i.e., the existential that wraps the actual element

Additionally, it is reasonable to want to constrain the associated types of an existential, e.g., "a Sequence whose element type is String" could be expressed by putting a where clause into protocol<...> or Any<...> (per "Renaming protocol<...> to Any<...>"):

let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]

The leading . indicates that we're talking about the dynamic type, i.e., the Self type that's conforming to the Sequence protocol. There's no reason why we cannot support arbitrary where clauses within the Any<...>.

And from being able to type a value as a protocol with an associated type, it's but a short step to allow for type-casting to that given type, and thus allow something like your first extension to compile.

这篇关于为什么我们不能将类型转换为具有关联类型的协议类型,却可以使用泛型实现相同的效果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆