Removing enumerated?


(Patrick Pijnappel) #37

Note that zip(a.indices, a) requires advancing the index twice each
iteration, which for non-array collections is not necessarily super
trivial. Considering we're talking about such a low-level operation as
iterating through a collection this might be worth considering.

Personally I'm in favor of adding indexed().

···

On Mon, Feb 6, 2017 at 3:14 AM, Ben Cohen via swift-evolution < swift-evolution@swift.org> wrote:

On Feb 5, 2017, at 08:12, Ben Cohen <ben_cohen@apple.com> wrote:

>> On Feb 4, 2017, at 14:43, Dave Abrahams via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>>
>> on Fri Feb 03 2017, Ben Cohen <swift-evolution@swift.org> wrote:
>>
>>>> On Feb 3, 2017, at 3:27 PM, Dave Abrahams via swift-evolution > >>>> <swift-evolution@swift.org> wrote:
>>>>
>>>> I don't always make zip a method, but when I do, its argument label is
>>>> “to:”
>>>
>>> Hmm, that doesn’t sound very natural to me.
>>>
>>> Then again the problem with “zip(with:)" is it’s already kind of a
>>> term of art for a version that takes a function to combine the two
>>> values.
>>>
>>> There’s also the question of how to spell a non-truncating versions
>>> (returning optionals or taking a pad value).
>>
>> Is there a use-case for such a zip?
>>
>
> Whenever it's not OK to not silently discard the elements in the longer
list (which can be a correctness trap of zip if you're not careful). Say
you're matching up contestants from two groups, but want to give byes to
the unmatched contestants in the larger group. Or you're generating a list
of positioned racers in a 8-car race, putting in a computer player when you
run out of real players.
>

Gah, accidental double-negation, meant "not OK to silently discard"

>> --
>> -Dave
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ben Cohen) #38

Note that zip(a.indices, a) requires advancing the index twice each iteration, which for non-array collections is not necessarily super trivial. Considering we're talking about such a low-level operation as iterating through a collection this might be worth considering.

Personally I'm in favor of adding indexed().

This isn't necessarily as much of a slam-dunk as you might think. Just as index advancement can theoretically be expensive, so can subscripting (though both need to be constant time, the constant factor could be high for either). Unless we made indexed() a protocol customization point rather than an extension (unlikely – we need to keep the number of those under control) and the collection provided a customized version, indexed() would need to use subscripting to return the element given the index it's tracking. Whereas iteration, being a much more limited forward-only API, might be implemented to be more efficient. So it's one hypothetical cost vs another. Alternatively, indexed() could be implemented to use an iterator for the elements part in parallel to the index advancement, in which case it's identical to the zip version. At least with the zip version, it's transparent which strategy is being used. In practice, for any Collection both costs should ideally be kept as small as possible, and so their cost is hopefully not material often enough to be factored into the decision of whether indexed() should exist on Collection, which should be decided on API ergonomics grounds.

PS for the standard library collections at least, any benchmarks that find that indexing/subscripting is significantly faster than iteration should be raised as bugs against the std lib/compiler/optimizer depending on where the problem is :slight_smile:

···

On Feb 5, 2017, at 16:47, Patrick Pijnappel <patrickpijnappel@gmail.com> wrote:

On Mon, Feb 6, 2017 at 3:14 AM, Ben Cohen via swift-evolution <swift-evolution@swift.org> wrote:
On Feb 5, 2017, at 08:12, Ben Cohen <ben_cohen@apple.com> wrote:

>> On Feb 4, 2017, at 14:43, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
>>
>>
>> on Fri Feb 03 2017, Ben Cohen <swift-evolution@swift.org> wrote:
>>
>>>> On Feb 3, 2017, at 3:27 PM, Dave Abrahams via swift-evolution >> >>>> <swift-evolution@swift.org> wrote:
>>>>
>>>> I don't always make zip a method, but when I do, its argument label is
>>>> “to:”
>>>
>>> Hmm, that doesn’t sound very natural to me.
>>>
>>> Then again the problem with “zip(with:)" is it’s already kind of a
>>> term of art for a version that takes a function to combine the two
>>> values.
>>>
>>> There’s also the question of how to spell a non-truncating versions
>>> (returning optionals or taking a pad value).
>>
>> Is there a use-case for such a zip?
>>
>
> Whenever it's not OK to not silently discard the elements in the longer list (which can be a correctness trap of zip if you're not careful). Say you're matching up contestants from two groups, but want to give byes to the unmatched contestants in the larger group. Or you're generating a list of positioned racers in a 8-car race, putting in a computer player when you run out of real players.
>

Gah, accidental double-negation, meant "not OK to silently discard"

>> --
>> -Dave
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #39

Note that zip(a.indices, a) requires advancing the index twice each
iteration, which for non-array collections is not necessarily super
trivial. Considering we're talking about such a low-level operation as
iterating through a collection this might be worth considering.

If you're worried about this, there's a really easy solution: write your
loop over a.indices and the subscript a using those indices to get the
element.

Personally I'm in favor of adding indexed().

As mentioned earlier in the thread, I'm strongly opposed to adding it at
this time.

···

on Sun Feb 05 2017, Patrick Pijnappel <swift-evolution@swift.org> wrote:

--
-Dave


#40

enumerated() is really useful. But leads to risky situations as described in the OP.

Couldn't it be named differently for Array and ArraySlice ?

The following code will crash at runtime, because b is ArraySlice and not Array (which can easily get unnoticed)
var a = ["Hello", "Cruel", "World!"]
var b = a.dropFirst()
for (i, e) in b.enumerated() {
if e.hasPrefix("C") {
b[i] = e.uppercased()
}
}

But this works without problem
var a = ["Hello", "Cruel", "World!"]
var b = Array(a.dropFirst())
for (i, e) in b.enumerated() {
if e.hasPrefix("C") {
b[i] = e.uppercased()
}
}

If enumerated was changed to enumeratedSlice(), such an error would be detected at compile time.


(Joe Groff) #41

This is exactly the situation where you should use indices instead, which does the right thing for any collection.


#42

Should I understand that a proposal would be to drop enumerated() ?


(Jean-Daniel) #43

Why would you do that. enumerated has not the right semantic for what you are trying to achieve, but it does not make it useless.


(Jon Hull) #44

Just spitballing here. Why don't we just change it to have the signature:

func enumerated(from: Int = 0) -> EnumeratedSequence<Self>

Then you can enumerate indicies using:

mySeq.enumerated(from: mySeq.startIndex)

The from version popping up in autocomplete may cause people to consider where it starts. Seems like an easy change that doesn't break anything or add maintenance burden...

Edit: I would also add that for UI or user facing purposes, it is nice to have the ability to start from 1...


(Jordan Rose) #45

startIndex isn't always an Int, and this wouldn't really be recommended practice for ArraySlice.

I do think enumerated is a little redundant with zip now that we have one-sided ranges: zip(1..., mySeq). But it's not so bad that we need to take it out.


(Jon Hull) #46

Could you explain more about why it wouldn't work with ArraySlice?


#47

My question was about : how to avoid an unsafe use of enumerated that causes a crash at runtime.

Yes, if used correctly (which was not the case in my first example), there is no problem with it. But I feel, this is not fully meeting the design goal of Swift to be a safe language.

Ideas similar to Jon_Hull's could be a way ?


(Jordan Rose) #48

The important point is that it wouldn't work for, say, String.

It would work for ArraySlice, but it wouldn't be the recommended way to work with ArraySlice because it's perpetuating a misunderstanding of Swift's collection model: the subscript operation on a collection does not take an offset; it takes an index. Offsets and indexes are the same for Array and pretty much no other collections, including ArraySlice (and Foundation.Data).


(Dave Abrahams) #49

"Safe" doesn't mean "never traps;" it means "never accesses uninitialized memory, or accesses memory as some type other than what was initialized there." Trapping is Swift's way of avoiding such unsafe accesses.


#50

The fact that this misunderstanding keeps cropping up, year after year, time and time and time again, tells me that to a large number of people out there, “safe” in the context of a programming language *does* mean “never traps”.

The property you are describing is properly termed “memory safe”. It has become commonplace in Swift parlance to abbreviate “memory safe” as “safe”, but evidence indicates that this abbreviation is not readily understood, nor in widespread use.

I think the most prudent course of action is to stop using that abbreviation, because it does not convey the desired information in practice. Either Swift should come up with a different abbreviation, or just say “memory safe” without abbreviating it.

After all, clarity at the point of use is more important than brevity.

• • •

As an illustrative example, suppose someone were to write the autopilot program for a self-driving car in Swift. If that program traps at runtime, well golly gosh, it certainly did avoid accessing the wrong piece of memory. But, by trapping at runtime, the real-world outcome could well be an automobile collision that causes actual human injury or death.

That is definitely not “safe” by any reasonable definition of the term.

Therefore, using “safe” to refer exclusively to “memory safe” is not reasonable.


(Jordan Rose) #51

You can make this distinction, but if the program does not trap at run time and instead, say, returns nil from findPedestrianInMyPath, then you still get an automobile collision that causes actual human injury or death.

Using "safe" to refer to "the program doesn't crash" is a bad idea, and I think we should continue correcting that misapprehension whether or not we decide to stop using the term "safe" ourselves.


(Benjamin Mayo) #52

enumerated() is confusing in its current form. If we are ever going to change anything about it, I'd rather we go the whole hog and properly remove it, rather than patch on default arguments or whatever to try to make it semantically relevant.

The reality is Swift's source-breaking policy means that unless enumerated() is actively harmful, it should just stay as is for the foreseeable future. I can't make strong arguments that it is harmful. It's 'fine'.


(Dave Abrahams) #53

Undoubtedly. It doesn't change the fact that that isn't what we mean when we say “Swift is a safe language,” and @claude31 has misunderstood the design goal, however poorly it may be stated.

It is also true that we try to write our APIs to prevent confusion and misuse at compile-time, which could be a more casual interpretation of the word “safe.” But it is impossible to design reasonable APIs without preconditions in any reasonable everyday programming language (I'm lookin' at you, division by zero), so Swift is going to violate a casual definition of the word “safe” if that's what you're expecting.

Hello, MemoryUnsafeMutableBufferPointer!


(Jens Persson) #54

: )


(Thomas Krajacic) #55

Can you elaborate on the misuse? I think you are referencing the use in https://github.com/vapor/core/blob/fda3ebda8046af7c9a886fa4a5cfd886111ac23e/Sources/Async/Future%2BFlatten.swift#L58 and while it enumerates a collection, it uses the index to build up a new Array with the collections elements. That's perfectly fine isn't it?


(Xiaodi Wu) #56

This discussion is many years old. Any misuse would be fixed by now; either I or someone else filed a bug back in the day.