[swift-evolution] [Idea] Replace enumerate() with something more explicit
Dave Abrahams
dabrahams at apple.com
Tue Apr 19 17:16:21 CDT 2016
on Fri Apr 15 2016, Brent Royal-Gordon <swift-evolution at swift.org> wrote:
> A discussion in the "mapValues" thread reminded me of a longstanding issue I have with Swift.
>
> `enumerate()` is an attractive nuisance. (That is, it looks like the
> thing you want, but it's not actually the right one.)
It depends what you want. It exists in part because it's nontrivial to
write a method that does what enumerate does, and does it efficiently.
> Most people use it because they want to enumerate over indices and
> elements at the same time.
If what you want is zip(a.indices, a), it's easy enough to write that.
> In reality, though, the first element of an
> `enumerate()` tuple is not the index—it's a monotonically increasing
> integer starting at 0. That *happens* to work for `Array`:
>
>> 1> let array = Array(0..<10)
>> 2. for (i, elem) in array.enumerate() {
>> 3. print(array[i])
>> 4. }
>> 0
>> 1
>> 2
>> 3
>> 4
>> 5
>> 6
>> 7
>> 8
>> 9
>
> But if you stray even a little bit from the golden path, things start to go wrong:
>
>> 5> let range = array[2..<8]
>> 6. for (i, elem) in range.enumerate() {
>> 7. print(range[i])
>> 8. }
>> fatal error: Index out of bounds
I think the potential for this confusion is inherent in the combination
of two factors:
* Arrays are indexed with integers
* The indices of a SubSequence are the indices of corresponding elements
in the thing it was sliced from.
I also think it has nothing whatsoever to do with enumerate.
> You can scarcely blame users for making this mistake, though—"The
> Swift Programming Language" encourages the misconception. "Iterating
> Over an Array" in "Collection Types":
>
>> If you need the integer index of each item as well as its value, use
>> the `enumerate()` method to iterate over the array instead. For each
>> item in the array, the `enumerate()` method returns a tuple composed
>> of the index and the value for that item.
>
> While the text is technically accurate—it only talks about iterating
> over arrays and the numbers generated by `enumerate()` happen to
> correspond to array indices—it creates a false implication that
> `enumerate()` is defined to return indices, which isn't true of other
> collections.
You should consider filing a bug report against TSPL, I think.
> This is made worse by the fact that `enumerate()` is not really a good
> name.
Totally open to better names. It's precedented in Python with exactly
these semantics, which is why we used it.
> It is not a common word, so people don't read it and immediately
> understand what it does; they memorize a Swift-specific meaning, and
> that meaning may incorporate the misconception that `enumerate()`
> includes indices. It is also not technically accurate: although it has
> "number" in its Latin roots, "enumerate" means either "to count" or
> "to list", not "to number" (i.e. assign numbers to). I know
> `enumerate()` is used in a couple of other languages (certainly
> Python, possibly others), but I don't think that overrides the fact
> that it's confusing.
Maybe "numbered()" would be a better name.
> I have three possible solutions to propose.
>
> OPTION ONE
>
> * Remove `enumerate()`.
I'm open to doing this if nobody feels the current semantics are useful
and we can get some real-world data showing that they're not.
> * Provide a type and postfix operator which allows you to write an
> infinite sequence as `0...`. (This might call a ClosedRange
> constructor which constrains the type to a protocol which provides
> `max`. This would imply that `FloatingPoint` and `Integer` protocols
> would need to conform to a common protocol declaring a `max` property,
> and in the case of `FloatingPoint`, `max` should probably be positive
> infinity.)
+1; I want this feature “anyway.” I also want to use it for slicing,
e.g. in lieu of x.dropFirst(3), x[3...]
> * Fix-It calls to `x.enumerate()` as `zip(0..., x)`. This is more
> complicated to look at, but it's *far* more explicit about what the
> operation actually does. (For bonus points, we could perhaps look at
> how the EnumerateSequence is used, and if the number is used as an
> index, go with `zip(x.indices, x)` instead.)
>
>
> OPTION TWO
>
> * Rename `enumerate()` to something more explicit, like
> `withIntegers()` or `numbered()`. (It might even make sense to add a
> `from:` parameter which defaults to 0.)
Hey (if we keep it) good idea!
> * Add to Collection an equivalent method with a similar name that
> provides indices, like `withIndices()` or `indexed()`.
Less enthused about this one, since you can get it trivially by
combining primitives.
> * Fix-It calls to `enumerate()` into either `numbered()` or
> `indexed()` (or whatever they end up being called) depending on the
> way they are used.
>
> OPTION THREE
>
> Combine the other two options:
>
> * Provide the infinite numeric sequence type from Option One.
>
> * Provide `numbered()` and `indexed()` (or whatever) methods which are
> explicitly typed to merely zip over the sequence/collection with an
> infinite integer sequence/Indices collection.
--
Dave
More information about the swift-evolution
mailing list