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

问题描述 投票:38回答:2

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

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

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

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

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

后者为什么不是:

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

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

oop generics swift syntax
2个回答
27
投票

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

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

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


25
投票

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

On Associated Types

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

当你看到:

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与用作其两个方法的参数的类型之间的连接 - 编译器自动确定这些类型与正确的方法相关联,因此它们满足协议对关联类型的要求。

On Generic Type Parameters

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

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

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

Further reading

Swift团队对泛型,协议和相关功能here有一个很好的写作。

© www.soinside.com 2019 - 2024. All rights reserved.