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

时间:2021-06-10 16:06:35

When I'm extending Collection the type of count is IndexDistance.

当我扩展Collection时,计数类型是IndexDistance。

When I'm extending Array type the count is of type Int

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

Why is there such a distinction? Is this a recent change or it's always been like this?

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

I've read this answer but couldn't pick up much.

我已经阅读了这个答案,但却无法满足。

The only thing I deemed related, but didn't understand was:

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

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

另一个优点是这个[IndexDistance]也适用于数组切片(其中第一个元素的索引不一定为零)

Not sure what that means.

不确定那是什么意思。

The reason I'm asking is that why does the code throw an error on Collection but doesn't do such on Array...even though both counts are ultimately an Int.

我问的原因是为什么代码会在Collection上抛出错误但在Array上没有这样做...即使两个计数最终都是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
        }
    }
}

EDIT:

编辑:

Based on comments made by Martin and others, I've added an extra question. Likely this is the root cause of my question...

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

Does it mean that within Collection type the IndexDistance isn't defined to Int. Basically in general at the 'Protocol' level associatedTypes aren't defined...It's waiting for a concrete type to do that? Is that right?

这是否意味着在Collection类型中,IndexDistance未定义为Int。基本上一般来说,在“协议”级别,关联类型没有定义......它正在等待具体类型来做到这一点?是对的吗?

That being said is there any meaningful use case for accessing count at the 'Protocol' level? I mean you can't compare it against any Int so it seems pretty useless.

话虽如此,是否有任何有意义的用例来访问“协议”级别的计数?我的意思是你无法将它与任何Int进行比较,所以它看起来很无用。

2 个解决方案

#1


10  

From Associated Types in the Swift Programming Language (emphasis added):

从Swift编程语言中的关联类型(重点补充):

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.

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

In Swift 3/4.0, the Collection protocol defines five associated types (from What’s in a Collection?):

在Swift 3 / 4.0中,Collection协议定义了五种相关类型(来自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>
    ...
}

Here

这里

    associatedtype IndexDistance: SignedInteger = Int

is an associated type declaration with a type constraint (: SignedInteger) and a default value (= Int),

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

If a type T adopts the protocol and does not define T.IndexDistance otherwise then T.IndexDistance becomes a type alias for Int. This is the case for many of the standard collection types (such as Array or String), but not for all. For example

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

public struct AnyCollection<Element> : Collection

from the Swift standard library defines

来自Swift标准库的定义

    public typealias IndexDistance = IntMax

which you can verify with

您可以验证

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

You can also define your own collection type with a non-Int index distance if you like:

如果您愿意,还可以使用非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
    }
}

Therefore, if you extend the concrete type Array then count is an Int:

因此,如果扩展具体类型Array,则count为Int:

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

But in a protocol extension method

但是在协议扩展方法中

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

everything you know is that the type of cnt is some type adopting the SignedInteger protocol, but that need not be Int. One can still work with the count, of course. Actually the compiler error in

您知道的一切是cnt的类型是采用SignedInteger协议的某种类型,但不一定是Int。当然,人们仍然可以使用伯爵。实际上编译错误了

    for index in 0...count { //  binary operator '...' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'

is misleading. The integer literal 0 could be inferred as a Collection.IndexDistance from the context (because SignedInteger conforms to ExpressibleByIntegerLiteral). But a range of SignedInteger is not a Sequence, and that's why it fails to compile.

是误导。整数文字0可以从上下文推断为Collection.IndexDistance(因为SignedInteger符合ExpressibleByIntegerLiteral)。但是一系列SignedInteger不是一个序列,这就是它无法编译的原因。

So this would work, for example:

所以这可行,例如:

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

As of Swift 4.1, IndexDistance is no longer used, and the distance between collection indices is now always expressed as an Int, see

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

In particular the return type of count is Int. There is a type alias

特别是计数的返回类型是Int。有一个类型别名

typealias IndexDistance = Int

to make older code compile, but that is remarked deprecated and will be removed in a future version of Swift.

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

#2


0  

Not exactly an answer but being the OP I think this was a vital prerequisite to my understanding.

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

  • I didn't know that you can constrain the associatedType of a protocol
  • 我不知道你可以约束协议的associatedType
  • I didn't know you can give the associatedType a default type
  • 我不知道你可以给associatedType一个默认类型
  • I didn't know conformance to a protocol's associatedType can be done through using a typeAlias.
  • 我不知道协议的关联类型是否可以通过使用typeAlias来完成。
  • I didn't know conformance to a protocol's associatedType can be done through other ways as well ie though defaulting.
  • 我不知道协议的关联类型的一致性也可以通过其他方式完成,即通过默认。
  • Probably the most important thing I didn't know is that by design the default type of the associatedType isn't triggered 'at the protocol level' ie it only gets constrained to its constrained Type. However once a class/struct adopts it...then and only then the default type is used. For more refer to Martin's answer above and Apple docs on associatedType.
  • 可能最重要的是我不知道的是,通过设计,关联类型的默认类型不会在“协议级别”触发,即它只会被约束到其约束类型。但是,一旦类/结构采用它...然后只使用默认类型。有关更多信息,请参阅Martin上面的回答和关于关联类型的Apple文档。
  • There is a third way of conformance to protocol's associatedType. Please see the link provided at the end. Basically, you can conform by defining the associatedType implicitly
  • 第三种方式符合协议的关联类型。请参阅最后提供的链接。基本上,您可以通过隐式定义associatedType来实现

Three different Protocols

// 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
}

Conformance to the protocols

// 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'
}

If you can understand why class SomeClass8 works without errors then you've got your answer!

如果您能理解为什么SomeClass8课程没有错误,那么您就得到了答案!


A very simple read can be found at here. I really like how the post defines the difference between implicit and explicit conformance to the protocol's associatedTypes

这里可以找到一个非常简单的读物。我非常喜欢这篇文章如何定义对协议的associatedTypes的隐式和显式一致性之间的区别

#1


10  

From Associated Types in the Swift Programming Language (emphasis added):

从Swift编程语言中的关联类型(重点补充):

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.

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

In Swift 3/4.0, the Collection protocol defines five associated types (from What’s in a Collection?):

在Swift 3 / 4.0中,Collection协议定义了五种相关类型(来自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>
    ...
}

Here

这里

    associatedtype IndexDistance: SignedInteger = Int

is an associated type declaration with a type constraint (: SignedInteger) and a default value (= Int),

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

If a type T adopts the protocol and does not define T.IndexDistance otherwise then T.IndexDistance becomes a type alias for Int. This is the case for many of the standard collection types (such as Array or String), but not for all. For example

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

public struct AnyCollection<Element> : Collection

from the Swift standard library defines

来自Swift标准库的定义

    public typealias IndexDistance = IntMax

which you can verify with

您可以验证

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

You can also define your own collection type with a non-Int index distance if you like:

如果您愿意,还可以使用非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
    }
}

Therefore, if you extend the concrete type Array then count is an Int:

因此,如果扩展具体类型Array,则count为Int:

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

But in a protocol extension method

但是在协议扩展方法中

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

everything you know is that the type of cnt is some type adopting the SignedInteger protocol, but that need not be Int. One can still work with the count, of course. Actually the compiler error in

您知道的一切是cnt的类型是采用SignedInteger协议的某种类型,但不一定是Int。当然,人们仍然可以使用伯爵。实际上编译错误了

    for index in 0...count { //  binary operator '...' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'

is misleading. The integer literal 0 could be inferred as a Collection.IndexDistance from the context (because SignedInteger conforms to ExpressibleByIntegerLiteral). But a range of SignedInteger is not a Sequence, and that's why it fails to compile.

是误导。整数文字0可以从上下文推断为Collection.IndexDistance(因为SignedInteger符合ExpressibleByIntegerLiteral)。但是一系列SignedInteger不是一个序列,这就是它无法编译的原因。

So this would work, for example:

所以这可行,例如:

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

As of Swift 4.1, IndexDistance is no longer used, and the distance between collection indices is now always expressed as an Int, see

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

In particular the return type of count is Int. There is a type alias

特别是计数的返回类型是Int。有一个类型别名

typealias IndexDistance = Int

to make older code compile, but that is remarked deprecated and will be removed in a future version of Swift.

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

#2


0  

Not exactly an answer but being the OP I think this was a vital prerequisite to my understanding.

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

  • I didn't know that you can constrain the associatedType of a protocol
  • 我不知道你可以约束协议的associatedType
  • I didn't know you can give the associatedType a default type
  • 我不知道你可以给associatedType一个默认类型
  • I didn't know conformance to a protocol's associatedType can be done through using a typeAlias.
  • 我不知道协议的关联类型是否可以通过使用typeAlias来完成。
  • I didn't know conformance to a protocol's associatedType can be done through other ways as well ie though defaulting.
  • 我不知道协议的关联类型的一致性也可以通过其他方式完成,即通过默认。
  • Probably the most important thing I didn't know is that by design the default type of the associatedType isn't triggered 'at the protocol level' ie it only gets constrained to its constrained Type. However once a class/struct adopts it...then and only then the default type is used. For more refer to Martin's answer above and Apple docs on associatedType.
  • 可能最重要的是我不知道的是,通过设计,关联类型的默认类型不会在“协议级别”触发,即它只会被约束到其约束类型。但是,一旦类/结构采用它...然后只使用默认类型。有关更多信息,请参阅Martin上面的回答和关于关联类型的Apple文档。
  • There is a third way of conformance to protocol's associatedType. Please see the link provided at the end. Basically, you can conform by defining the associatedType implicitly
  • 第三种方式符合协议的关联类型。请参阅最后提供的链接。基本上,您可以通过隐式定义associatedType来实现

Three different Protocols

// 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
}

Conformance to the protocols

// 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'
}

If you can understand why class SomeClass8 works without errors then you've got your answer!

如果您能理解为什么SomeClass8课程没有错误,那么您就得到了答案!


A very simple read can be found at here. I really like how the post defines the difference between implicit and explicit conformance to the protocol's associatedTypes

这里可以找到一个非常简单的读物。我非常喜欢这篇文章如何定义对协议的associatedTypes的隐式和显式一致性之间的区别