为什么协议的关联类型在Swift中不使用泛型类型语法?

oro*_*ome 46 oop generics syntax swift

我对用于协议的关联类型的语法与另一方面的泛型类型之间的区别感到困惑.

例如,在Swift中,可以使用类似的东西定义泛型类型

struct Stack<T> {
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
}
Run Code Online (Sandbox Code Playgroud)

而一个使用类似的东西定义一个具有相关类型的协议

protocol Container {
    typealias T
    mutating func append(item: T)
    var count: Int { get }
    subscript(i: Int) -> T { get }
}
Run Code Online (Sandbox Code Playgroud)

后者为什么不是:

protocol Container<T> {
    mutating func append(item: T)
    var count: Int { get }
    subscript(i: Int) -> T { get }
}
Run Code Online (Sandbox Code Playgroud)

是否存在一些深层次(或者可能只是显而易见并且迷失在我身上)的原因,即语言没有采用后一种语法?

Rob*_*ier 31

这个已在开发人员名单上覆盖了几次.基本答案是关联类型比类型参数更灵活.虽然这里有一个类型参数的特定情况,但很可能有几个.例如,Collections具有Element类型,但也有Index类型和Generator类型.如果你完全使用类型参数化将它们专门化,你就必须谈论类似Array<String, Int, Generator<String>>的事情.(这将允许我创建由Int以外的东西下标的数组,这可能被认为是一个功能,但也增加了很多复杂性.)

可以跳过所有这些(Java确实如此),但是你可以用更少的方法限制你的类型.事实上,Java在限制类型方面的作用非常有限.Java中的集合不能有任意索引类型.Scala使用与Swift相关的类型扩展了Java类型系统.相关类型在Scala中非常强大.它们也是混乱和头发撕裂的常规来源.

这种额外的力量是否值得,这是一个完全不同的问题,只有时间会证明.但是相关类型肯定比简单类型参数化更强大.

  • 他们是不同的。关联类型是两种类型之间的关系,由实现选择。泛型由调用者选择。所以数组总是有一个 Int 索引。如果我们使用泛型索引而不是关联类型,那么每次使用`Array(表示`Int`索引)时都必须说`Array&lt;Element, Int&gt;`,并且它与`Array&lt;不兼容Element、DictionaryIndex&gt;`(这将是合法的),否则每个 Collection 都必须使用 `Int` 作为其索引(目前大多数情况下都没有)。 (3认同)
  • 有趣的。这作为关联类型的参数是有意义的,作为一种额外的(更灵活的)专门化方式,但为什么不允许协议的类型参数,因为它们被允许用于泛型类型:在简单的情况下,它们可以完成工作? (2认同)

ric*_*ter 29

RobNapier的回答(像往常一样)非常好,但只是为了另一个可能证明进一步具有启发性的观点......

关联类型

协议是一组抽象的需求 - 具体类型必须满足的核对表才能说它符合协议.传统上,人们会想到行为的清单:由具体类型实现的方法或属性.关联类型是一种命名此类核对表中涉及的事物的方式,从而扩展定义,同时保持开放式关于符合类型如何实现一致性.

当你看到:

protocol SimpleSetType {
    associatedtype Element
    func insert(_ element: Element)
    func contains(_ element: Element) -> Bool
    // ...
}
Run Code Online (Sandbox Code Playgroud)

这意味着,对于声称符合的类型SimpleSetType,不仅必须包含该类型insert(_:)contains(_:)函数,这两个函数必须采用相同类型的参数.但是该参数的类型无关紧要.

您可以使用通用或非泛型类型实现此协议:

class BagOfBytes: SimpleSetType {
    func insert(_ byte: UInt8) { /*...*/ }
    func contains(_ byte: UInt8) -> Bool { /*...*/ }
}

struct SetOfEquatables<T: Equatable>: SimpleSetType {
    func insert(_ item: T) { /*...*/ }
    func contains(_ item: T) -> Bool { /*...*/ }
}    
Run Code Online (Sandbox Code Playgroud)

请注意,没有任何地方BagOfBytesSetOfEquatables定义SimpleSetType.Element用作其两个方法的参数的类型和类型之间的连接- 编译器自动确定这些类型与正确的方法相关联,因此它们满足协议对关联类型的要求.

关于通用类型参数

如果关联类型扩展了用于创建抽象清单的词汇表,则泛型类型参数会限制具体类型的实现.如果你有这样的泛型类:

class ViewController<V: View> {
    var view: V
}
Run Code Online (Sandbox Code Playgroud)

它并没有说有很多不同的方法来制作一个ViewController(只要你有一个view),它说a ViewController 是一个真实的,具体的东西,它有一个view.而且,我们并不确切知道任何给定ViewController实例的视图类型,但我们确实知道它必须是View(类的子View类,或实现View协议的类型......我们不说) .

或者换句话说,编写泛型类型或函数是编写实际代码的快捷方式.举个例子:

func allEqual<T: Equatable>(a: T, b: T, c: T) {
    return a == b && b == c
}
Run Code Online (Sandbox Code Playgroud)

这与您浏览所有Equatable类型并写入的效果相同:

func allEqual(a: Int, b: Int, c: Int) { return a == b && b == c }
func allEqual(a: String, b: String, c: String) { return a == b && b == c }
func allEqual(a: Samophlange, b: Samophlange, c: Samophlange) { return a == b && b == c }
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我们在这里创建代码,实现新行为 - 与协议相关类型非常不同,我们只描述了其他要实现的要求.

TLDR

关联类型和泛型类型参数是非常不同的工具:关联类型是描述语言,泛型是实现语言.它们有着截然不同的用途,即使它们的用途有时看起来相似(特别是当涉及到任何元素类型集合的抽象蓝图和仍然可以有任何元素类型的实际集合类型之间的细微差别.通用元素).因为它们是非常不同的野兽,它们具有不同的语法.

进一步阅读

雨燕的团队有仿制药,协议和相关功能一个很好的书面记录在这里.

  • 我绝对不会得到关于泛型类型参数和相关类型是不同类型工具的论点.从具体类型开始,这两种仪器都使您的类型更加通用*.这基本上是一回事.唯一的区别在于语法以及如何约束类型参数/关联类型值的方式. (4认同)