[swift-evolution] [Idea] Add an (Index, Element) sequence to CollectionType

Kevin Ballard kevin at sb.org
Tue Dec 29 00:27:53 CST 2015


What you describe isn't a bug in the implementation, it's a bug in the
expectation that you can mutate an array that you're iterating over.
With any other collection, the mutation would actually invalidate the
index. It so happens that Arrays use Int as their index, so they can't
really be considered invalid, but you should treat them the same way in
cases like this; mutating the array means the index range you're
iterating over is no longer representative of the array.

-Kevin Ballard

On Mon, Dec 28, 2015, at 06:30 PM, Dany St-Amant via swift-evolution wrote:
>
> The original example contains a bug which is present on all looping
> version/alternative due to the mutating nature of the array. Using the
> zip implementation:
>
> var array = [ "", "a", "b", "c", "", "d" ] var secondHalf =
> array[array.count/2..<array.count] for (index, element) in
> zip(secondHalf.indices, secondHalf) { if element == "" {
> secondHalf.removeAtIndex(index)    } }
>
> The variable index cycles through 3,4 and 5; but in order to be able
> to remove the right element beyond the first removal, the variable
> index should have cycled through 3, 4 and 4 (as some elements got
> shifted after the first mutation). Mutating the array/list which one
> loops over is a risky business and is a nice source of bugs (including
> infinite loop). If this (Index, Element) is further investigated, it
> should consider that one may also want to do a insert(:atIndex:), and
> may expect the (Index, Element) to have proper Index but only for the
> original Element.
>
> Dany St-Amant
>
>
>> Le 28 déc. 2015 à 01:06, Kevin Ballard via swift-evolution <swift-
>> evolution at swift.org> a écrit :
>>
>> What you're asking for can already be done with `zip(col.indices,
>> col)`. And in my experience the need for this sort of thing is rare
>> enough that there's no need to have a dedicated property for it in
>> the stdlib. The few times that I've needed this sort of thing, I've
>> always just said
>>
>> for index in col.indices {    let elt = col[index]    // ... }
>>
>> and that's pretty simple. But if I ever did need to map it, I'd just
>> use the aforementioned zip() expression.
>>
>> -Kevin Ballard
>>
>> On Sun, Dec 27, 2015, at 12:08 AM, Patrick Pijnappel via swift-
>> evolution wrote:
>>> -- Introduction
>>>
>>> There should be a property on CollectionType that returns a sequence
>>> of (Index, Element) tuples. Currently enumerate() is often used
>>> instead, but it is not well suited to the task and can lead to bugs.
>>>
>>>
>>>
>>> -- Motivation
>>>
>>> Using enumerate() instead of an (Index, Element) sequence has two
>>> main problems. Both arise because enumerate() returns a sequence of
>>> (n, Element) tuples, where n is the element *number*, instead of a
>>> sequence of (Index, Element).
>>>
>>> 1) It doesn't work for collections not indexed by integers.
>>>
>>> 2) It doesn't do what you might expect in some cases, as indices do
>>>    not always start at 0. For example ArraySlice's indices do not:
>>>    array[2..<5] starts with index 2. Consider the following code to
>>>    take the 2nd half of the array and remove all empty elements:
>>>
>>> var array = [ "", "a", "b", "c", "", "d" ] var secondHalf =
>>> array[array.count/2..<array.count] for (index, element) in
>>> secondHalf.enumerate() { if element == "" {
>>> secondHalf.removeAtIndex(index) } }
>>>
>>> This code will crash (ignoring for a moment this should probably be
>>> using filter).
>>>
>>>
>>>
>>> -- Alternatives
>>>
>>> The same effect can already be achieved using the following:
>>>
>>> for index in collection.indices {  let element = collection[index]
>>> // ... }
>>>
>>> However having a dedicated (Index, Element) sequence has the
>>> following advantages:
>>> a) It can help prevent people from using enumerate()
>>>    inappropriately.
>>> b) It is very common use case that deserves shortening.
>>> c) It can be chained (e.g. to map).
>>>
>>>
>>>
>>> -- Proposed Solution
>>>
>>> Add a property/method on CollectionType that returns a sequence of
>>> (Index, Element) tuples. For example, using a property named
>>> indexed:
>>>
>>> for (index, element) in collection.indexed {  // ... }
>>>
>>> This should be the preferred idiom when you want both the index and
>>> the element.
>>>
>>> Note that enumerate() does still have valid roles to play:
>>> - When you actually do want the element number, not the index.
>>> - When you have a SequenceType, as it isn't indexed.
>>>
>>>
>>>
>>> -- Implementation
>>>
>>> The feature could be entirely implemented using existing constructs:
>>>
>>> extension CollectionType {  var indexed: AnySequence<(Index,
>>> Generator.Element)> {    return AnySequence(indices.lazy.map { ($0,
>>> self[$0]) })  } }
>>>
>>> Alternatively, a dedicated SequenceType and/or GeneratorType could
>>> be added.
>>>
>>>
>>> _________________________________________________
>>> swift-evolution mailing list swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> _______________________________________________
>> swift-evolution mailing list swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
> _________________________________________________
> swift-evolution mailing list swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151228/d9abe971/attachment.html>


More information about the swift-evolution mailing list