on Fri Apr 15 2016, Brent Royal-Gordon <swift-evolution@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 _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution