为什么 Collection 与 Array 计数 return 不同类型?

Why does count return different types for Collection vs. Array?

当我扩展 Collectioncount 的类型是 IndexDistance.

当我扩展 Array 类型时,count 的类型是 Int

为什么会有这样的区分?这是最近的变化还是一直这样?

我读过这篇文章,但没学到多少。

唯一我认为相关但不理解的是:

Another advantage is that this[IndexDistance] also works correctly with array slices (where the index of the first element is not necessarily zero

不知道那是什么意思。

我问的原因是,为什么代码在 Collection 上抛出错误,但在 Array 上却没有...即使 counts 最终都是 Int .

extension Collection where Element: Comparable{
    func whatever(){
        for index in 0...count{ //  binary operator '...' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'

        }
    }
}

extension Array where Element: Comparable{
    func whatever(){
        for index in 0...count{ // NO ERROR
        }
    }
}

编辑:

根据 Martin 和其他人的评论,我添加了一个额外的问题。可能这就是我的问题的根本原因...

这是否意味着在 Collection 中键入 IndexDistance 定义为 Int。基本上一般在 'Protocol' 级别,associatedTypes 未定义 ...它正在等待 具体 类型来做到这一点?那正确吗?

也就是说,在 'Protocol' 级别访问 count 是否有任何有意义的用例?我的意思是你无法将它与任何 Int 进行比较,所以它看起来毫无用处。

来自 Swift 编程语言中的 Associated Types(强调已添加):

When defining a protocol, it’s sometimes useful to declare one or more associated types as part of the protocol’s definition. An associated type gives a placeholder name to a type that is used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.

Swift3/4.0,Collection协议定义了五种关联类型 (来自 What’s in a Collection?):

protocol Collection: Indexable, Sequence {
    associatedtype Iterator: IteratorProtocol = IndexingIterator<Self>
    associatedtype SubSequence: IndexableBase, Sequence = Slice<Self>
    associatedtype Index: Comparable // declared in IndexableBase
    associatedtype IndexDistance: SignedInteger = Int
    associatedtype Indices: IndexableBase, Sequence = DefaultIndices<Self>
    ...
}

这里

    associatedtype IndexDistance: SignedInteger = Int

是一个带有类型约束(: SignedInteger)和默认值(= Int)的关联类型声明,

如果类型 T 采用协议并且没有定义 T.IndexDistance,否则 T.IndexDistance 成为 Int 的类型别名。 许多标准集合类型都是这种情况 (例如 ArrayString),但并非适用于所有人。例如

public struct AnyCollection<Element> : Collection

来自Swift标准库定义

    public typealias IndexDistance = IntMax

你可以用

验证
let ac = AnyCollection([1, 2, 3])
let cnt = ac.count
print(type(of: cnt)) // Int64

如果愿意,您还可以使用非Int 索引距离定义自己的集合类型:

struct MyCollection : Collection {

    typealias IndexDistance = Int16
    var startIndex: Int { return  0 }
    var endIndex: Int { return  3 }

    subscript(position: Int) -> String {
        return "\(position)"
    }

    func index(after i: Int) -> Int {
        return i + 1
    }
}

因此,如果扩展具体类型 Array 那么 count 是一个 Int:

extension Array {
    func whatever() {
        let cnt = count // type is `Int`
    }
}

但是在协议扩展方法中

extension Collection {
    func whatever() {
        let cnt = count // some `SignedInteger`
    }
}

你所知道的是cnt的类型是一些类型采用 SignedInteger 协议,但不一定是 Int。一个还可以 当然是和伯爵一起工作。实际上

中的编译器错误
    for index in 0...count { //  binary operator '...' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'

具有误导性。整数文字 0 可以推断为 Collection.IndexDistance 从上下文(因为 SignedInteger 符合 ExpressibleByIntegerLiteral)。但是 SignedInteger 的范围不是 Sequence,这就是编译失败的原因。

所以这会起作用,例如:

extension Collection {
    func whatever() {
        for i in stride(from: 0, to: count, by: 1) {
            // ...
        }
    }
}

Swift 4.1 开始, IndexDistance 不再使用,并且 集合索引之间的距离现在总是表示为 Int,参见

特别是 count 的 return 类型是 Int。有类型别名

typealias IndexDistance = Int

使旧代码编译,但已弃用并将被删除 在 Swift.

的未来版本中

不完全是答案,但作为 OP,我认为这些都是我理解的重要先决条件。我不知道:

  • 您可以限制协议的associatedtype
  • 你可以给associatedtype一个默认类型
  • 遵守协议的 associatedtype 可以 通过使用 typealias 来完成。
  • 遵守协议的associatedtype也可以通过其他方式完成,即默认
  • 根据设计 默认 类型的 associatedType 不会被触发 'at the protocol level' 即它只会被触发约束到它的约束类型。然而,一旦 class/struct 采用了它……然后并且只有在那时才会使用默认类型。有关更多信息,请参阅 above and Apple docs on associatedtype
  • 还有第三种方法可以遵守协议 associatedtype。请看最后提供的link。基本上,您可以通过隐式定义 associatedtype
  • 也许最常见的方法是通过泛型约束来符合具有关联类型的协议。参见 SomeClass9

三种不同的协议

// associatedtype isn't constrained
protocol NotConstrained{
    associatedtype IndexDistance
}

// associatedtype is constrained
protocol Constrained{
    associatedtype IndexDistance: SignedInteger
}

// associatedtype is constrained and defaulted
protocol ConstrainedAndDefaulted{
    associatedtype IndexDistance: SignedInteger = Int
}

遵守协议

// All Good
class someClass1: NotConstrained{
    typealias IndexDistance = Int
}

// All Good
class someClass2: NotConstrained{
    typealias IndexDistance = String // It works with String as well, since it wasn't constrained
}

// Not Good
class SomeClass3: NotConstrained{
    // error: type 'SomeClass3' does not conform to protocol 'NotConstrained'
    // doesn't work because we MUST have a typealias
}

// All Good
class SomeClass4: Constrained{
    typealias IndexDistance = Int16
}

// Not Good
class SomeClass5: Constrained{
    typealias IndexDistance = String
    // error: type 'SomeClass5' does not conform to protocol 'Constrained'
    // Obviously! Because String isn't of type 'SignedIngeter'
}

// Not Good
class SomeClass6: Constrained{
    // error: type 'SomeClass6' does not conform to protocol 'Constrained'        
}

// All Good
class SomeClass7: ConstrainedAndDefaulted{
    // NO ERROR, because the associatedtype has already defaulted
}

// All Good
class SomeClass8: ConstrainedAndDefaulted{
    typealias IndexDistance = Int64 // We changed the default from 'Int' to 'Int64'
    // Which is ok because 'Int64' is of type 'SignedInteger'
}

class SomeClass9<T> : NotConstrained {
    typealias IndexDistance = T
}

如果您能理解为什么 class SomeClass8 可以正常工作,那么您就找到了答案!


可以在 here 找到一个非常简单的读物。我真的很喜欢 post 如何定义 implicitexplicit 之间的差异,以符合协议的关联类型

编辑:

Understanding protocol associated types and their constraints 教程是一本很棒的读物。

我将不得不返回此处并使用上述教程更新我的答案。但在那之前参考link。这真的很有帮助。