为什么count会为Collection vs. Array返回不同的类型?

时间:2018-03-28 16:47:34

标签: arrays swift collections count associated-types

当我Collection延长时count的类型为IndexDistance

当我延长Array时,count类型为Int

为什么会有这样的区别?这是最近的变化还是总是这样?

我已经读过这个answer,但无法获得太多。

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

  

另一个优点是这个[IndexDistance]也能正常工作   使用数组切片(第一个元素的索引不是   必须为零

不确定这意味着什么。

我问的原因是,为什么代码会在Collection上抛出错误但在Array上没有这样做...即使count最终都是{{1} }}

Int

修改

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

是否意味着在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 } } } 类型中,Collection 不是定义为IndexDistance。基本上一般在“协议”中#39; level associatedTypes 未定义 ...它正在等待具体的类型来执行此操作?是对的吗?

话虽如此,在“协议”中访问Int是否有任何有意义的用例。水平?我的意思是你不能将它与任何count进行比较,所以它似乎没用。

2 个答案:

答案 0 :(得分:10)

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

  

在定义协议时,将一个或多个关联类型声明为协议定义的一部分有时很有用。关联类型为占位符名称提供用作协议一部分的类型。 在采用协议之前,不会指定用于该关联类型的实际类型。使用associatedtype关键字指定关联类型。

Swift 3 / 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,那么countInt

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可以推断为a 来自上下文的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的返回类型是Int。有一个类型别名

typealias IndexDistance = Int

使旧的代码编译,但是已被弃用并且将被删除 在未来版本的Swift中。

答案 1 :(得分:1)

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

  • 您可以约束协议的associatedtype
  • 您可以为associatedtype提供默认类型
  • 通过使用associatedtype可以完成对协议的typealias 的遵守。
  • 协议associatedtype 的一致性可以通过其他方式完成,即默认
  • 按设计 associatedType默认类型未在协议级别触发&#39;即它只受限于其约束类型。然而,一旦类/结构采用它...然后只使用默认类型。有关详情,请参阅上面的Martin's answerassociatedtype
  • 上的Apple文档
  • 第三种方式符合协议associatedtype。请参阅最后提供的链接。基本上,您可以通过隐式定义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找到一个非常简单的读物。我非常喜欢这篇文章如何定义隐式显式与协议相关类型之间的区别

编辑:

Understanding protocol associated types and their constraints教程非常精彩。

我将不得不回到这里并使用上面的教程更新我的答案。但在此之前请参考链接。这真的很有帮助。