Collection Oddities


(Guillaume Lessard) #1

I keep running into weird things with Swift 3 Collections.

A) Collection has its count property defined as an Int via IndexDistance:

public protocol Collection : Indexable, Sequence {
    associatedtype IndexDistance : SignedInteger = Int // line 182 in Collection.swift
    public var count: IndexDistance { get } // line 776
}

Given this, why can’t `count` be treated as an Int?

func intCount<C: Collection>(of c: C) -> Int {
  return c.count // nope!
}

A numericCast is required here in order to “convert” what is known-to-be-an-Int to an actual Int.
Why this is so? IndexDistance is defined such that it must be an integer (“A type that represents the number of steps between a pair of indices”)...
What is gained by making it so generic that it can’t simply be an Int?

B) Collection.Indices is a Collection of Index, *but*
- Collection.Indices.Index is not the same type as Collection.IndexDistance
- Collection.Indices.Iterator.Element is somehow not the automatically the same type as Collection.Index
(ugh)

The second seems like a conditional conformance issue, but the first one is baffling.

I did find that with String.CharacterView:
String.CharacterView.Indices.Index != String.CharacterView.IndexDistance
although, as expected,
String.CharacterView.Indices.IndexDistance == String.CharacterView.IndexDistance
(which provides an clear workaround.)

···

***
I wanted to extend Collection with concurrentPerform; which seems simple on the surface:

extension Collection {
  public func concurrentPerform(task: @escaping (Self.Iterator.Element) -> Void) {
    DispatchQueue.concurrentPerform(iterations: count) {
      iteration in
      task(self[indices[iteration]])
    }
  }
}
… but that won’t do.

The closest thing I found that does work is this:

extension Collection {
  public func concurrentPerform(task: @escaping (Self.Iterator.Element) -> Void) {
    let count: Int = numericCast(self.count)
    let indexList = (0..<count).map { index(startIndex, offsetBy: numericCast($0)) }

    DispatchQueue.concurrentPerform(iterations: count) {
      iteration in
      task(self[indexList[iteration]])
    }
  }
}

Note the unfortunate creation of an array of Index.
Generic Collection code is exhausting!

Cheers,
Guillaume Lessard


(Slava Pestov) #2

I keep running into weird things with Swift 3 Collections.

A) Collection has its count property defined as an Int via IndexDistance:

public protocol Collection : Indexable, Sequence {
   associatedtype IndexDistance : SignedInteger = Int // line 182 in Collection.swift
   public var count: IndexDistance { get } // line 776
}

This declaration specifies that the *default* associated type is Int, not that it’s *always* Int. A Collection implementation is free to use a different type as its IndexDistance if it wants.

Given this, why can’t `count` be treated as an Int?

func intCount<C: Collection>(of c: C) -> Int {
return c.count // nope!
}

A numericCast is required here in order to “convert” what is known-to-be-an-Int to an actual Int.
Why this is so? IndexDistance is defined such that it must be an integer (“A type that represents the number of steps between a pair of indices”)...
What is gained by making it so generic that it can’t simply be an Int?

B) Collection.Indices is a Collection of Index, *but*
- Collection.Indices.Index is not the same type as Collection.IndexDistance

There’s no reason for them to be the same type for a generic Collection, just because they both happen to be Int for Array, though.

- Collection.Indices.Iterator.Element is somehow not the automatically the same type as Collection.Index
(ugh)

I think you’re right that this is a generics limitation - we’re waiting on ‘where clauses for associated types’ to make these two types equivalent.

The second seems like a conditional conformance issue, but the first one is baffling.

I did find that with String.CharacterView:
String.CharacterView.Indices.Index != String.CharacterView.IndexDistance
although, as expected,
String.CharacterView.Indices.IndexDistance == String.CharacterView.IndexDistance
(which provides an clear workaround.)

***
I wanted to extend Collection with concurrentPerform; which seems simple on the surface:

extension Collection {
public func concurrentPerform(task: @escaping (Self.Iterator.Element) -> Void) {
   DispatchQueue.concurrentPerform(iterations: count) {
     iteration in
     task(self[indices[iteration]])
   }
}
}
… but that won’t do.

The closest thing I found that does work is this:

extension Collection {
public func concurrentPerform(task: @escaping (Self.Iterator.Element) -> Void) {
   let count: Int = numericCast(self.count)
   let indexList = (0..<count).map { index(startIndex, offsetBy: numericCast($0)) }

   DispatchQueue.concurrentPerform(iterations: count) {
     iteration in
     task(self[indexList[iteration]])
   }
}
}

Note the unfortunate creation of an array of Index.
Generic Collection code is exhausting!

Maybe in this case it makes more sense to define this on RandomAccessCollection or even just Array instead?

Slava

···

On Feb 7, 2017, at 8:14 PM, Guillaume Lessard via swift-users <swift-users@swift.org> wrote:

Cheers,
Guillaume Lessard

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Guillaume Lessard) #3

I see how I’d misunderstood that line.

This being said, does this particular freedom really bring anything to the table? It is simply a counter (“the number of steps between a pair of indices”). On one hand, a “collection" that has in excess of Int.max/2 elements likely needs a different protocol; on the other, using a shorter type for a counter seems retro.

Thanks!
Guillaume Lessard

···

On 7 févr. 2017, at 21:57, Slava Pestov <spestov@apple.com> wrote:

On Feb 7, 2017, at 8:14 PM, Guillaume Lessard via swift-users <swift-users@swift.org> wrote:

I keep running into weird things with Swift 3 Collections.

A) Collection has its count property defined as an Int via IndexDistance:

public protocol Collection : Indexable, Sequence {
  associatedtype IndexDistance : SignedInteger = Int // line 182 in Collection.swift
  public var count: IndexDistance { get } // line 776
}

This declaration specifies that the *default* associated type is Int, not that it’s *always* Int. A Collection implementation is free to use a different type as its IndexDistance if it wants.


(Dave Abrahams) #4

There are still plenty of 32-bit platforms that address 64-bit files,
which should be able to be modeled as Collections.

···

on Tue Feb 07 2017, Guillaume Lessard <swift-users-AT-swift.org> wrote:

On 7 févr. 2017, at 21:57, Slava Pestov <spestov@apple.com> wrote:

On Feb 7, 2017, at 8:14 PM, Guillaume Lessard via swift-users <swift-users@swift.org> wrote:

I keep running into weird things with Swift 3 Collections.

A) Collection has its count property defined as an Int via IndexDistance:

public protocol Collection : Indexable, Sequence {
  associatedtype IndexDistance : SignedInteger = Int // line 182 in Collection.swift
  public var count: IndexDistance { get } // line 776
}

This declaration specifies that the *default* associated type is
Int, not that it’s *always* Int. A Collection implementation is free
to use a different type as its IndexDistance if it wants.

I see how I’d misunderstood that line.

This being said, does this particular freedom really bring anything to
the table? It is simply a counter (“the number of steps between a pair
of indices”). On one hand, a “collection" that has in excess of
Int.max/2 elements likely needs a different protocol; on the other,
using a shorter type for a counter seems retro.

--
-Dave