繁体   English   中英

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

[英]Why don't associated types for protocols use generic type syntax in Swift?

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

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

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

这已在 devlist 中多次提及。 基本答案是关联类型比类型参数更灵活。 虽然这里有一个类型参数的特定情况,但很可能有几个。 例如,集合有一个元素类型,还有一个索引类型和一个生成器类型。 如果你完全用类型参数化来专门化它们,你就必须谈论诸如Array<String, Int, Generator<String>>之类的东西。 (这将允许我创建由 Int 以外的其他内容下标的数组,这可以被视为一个功能,但也会增加很多复杂性。)

可以跳过所有这些(Java 会这样做),但是您可以限制类型的方法较少。 实际上,Java 在约束类型方面非常有限。 在 Java 中,您的集合不能具有任意索引类型。 Scala 使用关联类型扩展了 Java 类型系统,就像 Swift 一样。 关联类型在 Scala 中非常强大。 它们也是混乱和撕裂头发的常见来源。

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

RobNapier 的回答(像往常一样)非常好,但只是从另一个角度来看可能会进一步启发......

关于关联类型

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

当你看到:

protocol SimpleSetType {
    associatedtype Element
    func insert(_ element: Element)
    func contains(_ element: Element) -> Bool
    // ...
}

这意味着,对于声明符合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 { /*...*/ }
}    

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

关于泛型类型参数

关联类型扩展了您创建抽象清单的词汇量,而泛型类型参数限制了具体类型的实现 当你有一个像这样的泛型类时:

class ViewController<V: View> {
    var view: V
}

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

或者换句话说,编写泛型类型或函数是编写实际代码的一种捷径。 拿这个例子:

func allEqual<T: Equatable>(a: T, b: T, c: T) {
    return a == b && b == c
}

这与您浏览所有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 }

正如你所看到的,我们在这里创建代码,实现新的行为——与协议关联类型非常不同,在协议相关类型中,我们只描述其他东西要满足的要求。

TLDR

关联类型和泛型类型参数是非常不同的工具:关联类型是一种描述语言,泛型是一种实现语言。 它们有非常不同的用途,尽管它们的用途有时看起来很相似(尤其是在涉及任何元素类型集合的抽象蓝图和仍然可以具有任何元素的实际集合类型之间的细微差别时通用元素)。 因为它们是非常不同的野兽,所以它们具有不同的语法。

进一步阅读

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

为了增加已经很好的答案,泛型和关联类型之间还有另一个很大的区别:类型泛型实现的方向。

在泛型类型的情况下,客户端决定了应该为泛型使用哪种类型,而对于具有关联类型的协议,则完全由类型本身控制。 这意味着符合关联类型的类型可以自由选择最适合他们的关联类型,而不是被迫使用他们不知道的某些类型。

正如其他人所说, Collection<\/code>协议是一个很好的例子,说明了为什么关联类型在某些情况下更合适。 协议看起来像这样(请注意,我省略了一些其他相关类型):

protocol Collection {
    associatedtype Element
    associatedtype Index

    ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM