[Pitch] Enumerate from offset


(Pavol Vaskovic) #1

Hello!

Here's a pitch to pick a small nit from the Sequence protocol:

https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md

Best regards
Pavol Vaskovic

Enumerate from offset

···

-

   Proposal: SE-NNNN
   <https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md>
   -

   Authors: Pavol Vaskovic <https://github.com/palimondo>, Author 2
   <https://github.com/swiftdev>
   -

   Review Manager: TBD
   -

   Status: Pitch
   -

   Bugs: SR-4746 <https://bugs.swift.org/browse/SR-4746>

<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#introduction>
Introduction

Let user specify the staring index for enumerated method on Sequences.

Swift-evolution thread: Discussion thread topic for that proposal
<https://lists.swift.org/pipermail/swift-evolution/>
<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#motivation>
Motivation

The enumerated() method defined in an extension on protocol Sequence always
counts from 0. When you need the numbers to be counting up from different
index, you have to post process the resulting tuple in an inconvenient way.
<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#proposed-solution>Proposed
solution

We could provide an option to count elements from a user specified offset:

[6, 7, 8].enumerated(from: 6)// [(offset: 6, element: 6), (offset: 7,
element: 7), (offset: 8, element: 8)]

If implemented with default parameter, this does not change the usage for
existing code, being source compatible with Swift 3.
<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#detailed-design>Detailed
design

The proposed solution is to propagate the starting value to the internal
counter on EnumeratedIterator and set the default starting value to 0.

public struct EnumeratedIterator<
    Base : IteratorProtocol> : IteratorProtocol, Sequence {
    internal var _base: Base
    internal var _count: Int

    /// Construct from a `Base` iterator. internal init(_base:
Base, _offset: Int) {
        self._base = _base
        self._count = _offset
    }

    /// The type of element returned by `next()`. public typealias
Element = (offset: Int, element: Base.Element)

    /// Advances to the next element and returns it, or `nil` if no
next element /// exists. /// /// Once `nil` has been
returned, all subsequent calls return `nil`. public mutating func
next() -> Element? {
        guard let b = _base.next() else { return nil }
        let result = (offset: _count, element: b)
        _count += 1
        return result
    }
}
public struct EnumeratedSequence<Base : Sequence> : Sequence {
    internal var _base: Base
    internal let _offset: Int

    /// Construct from a `Base` sequence. internal init(_base:
Base, _offset: Int) {
        self._base = _base
        self._offset = _offset
    }

    /// Returns an iterator over the elements of this sequence.
public func makeIterator() -> _EnumeratedIterator<Base.Iterator> {
        return EnumeratedIterator(_base: _base.makeIterator(), _offset: _offset)
    }
}
extension Sequence {
    public func enumerated(from: Int = 0) -> _numeratedSequence<Self> {
        return EnumeratedSequence(_base: self, _offset: from)
    }
}

<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#source-compatibility>Source
compatibility

Proposed change is source compatible with Swift 3.
<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#effect-on-abi-stability-and-resilience>Effect
on ABI stability and resilience

This change does affect the ABI and should be implemented before we freeze
it.
<https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md#alternatives-considered>Alternatives
considered

Currently proposed workaround for the lack of flexibility in enumerated() is
to use zip with the collection and half-open range. From SR-0172 One-sided
Ranges
<https://github.com/apple/swift-evolution/blob/master/proposals/0172-one-sided-ranges.md>
:

Additionally, when the index is a countable type, i... should form a
Sequence that counts up from i indefinitely. This is useful in forming
variants of Sequence.enumerated() when you either want them non-zero-based
i.e.zip(1..., greeting), or want to flip the order i.e. zip(greeting, 0...).

Drawback of this approach is that you need to use free function zip,
forcing a break in the chain of sequence operations, as there is currently
no zipped method on Sequence.

If this is the preffered approach, we should consider removing the
enumerated() method altogether, because the limited usefullness in its
current state hardly justifies the space on API surface it occupies.


Add forEachWithIndex to Array
(André Videla) #2

You can do this trivially with drop. But enumerated from has one nice property:

myArray.enumerated().dropFirst(6) // counts from 6

myArray.dropFirst(6).enumerated() // counts from 0

Those two lines do very different things even though they look similar

myArray.enumerated(from: 6)
Is unambiguous

But I don't think it is worth adding to the sequence protocol if the argument is that it's unambiguous.

Andre Videla

···

On 4 May 2017, at 14:39, Pavol Vaskovic via swift-evolution <swift-evolution@swift.org> wrote:

Hello!

Here's a pitch to pick a small nit from the Sequence protocol:

https://github.com/palimondo/swift-evolution/blob/sequence-cleanup/proposals/NNNN-enumerate-from.md

Best regards
Pavol Vaskovic

Enumerate from offset

Proposal: SE-NNNN

Authors: Pavol Vaskovic, Author 2

Review Manager: TBD

Status: Pitch

Bugs: SR-4746

Introduction

Let user specify the staring index for enumerated method on Sequences.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The enumerated() method defined in an extension on protocol Sequence always counts from 0. When you need the numbers to be counting up from different index, you have to post process the resulting tuple in an inconvenient way.

Proposed solution

We could provide an option to count elements from a user specified offset:

[6, 7, 8].enumerated(from: 6)
// [(offset: 6, element: 6), (offset: 7, element: 7), (offset: 8, element: 8)]
If implemented with default parameter, this does not change the usage for existing code, being source compatible with Swift 3.

Detailed design

The proposed solution is to propagate the starting value to the internal counter on EnumeratedIterator and set the default starting value to 0.

public struct EnumeratedIterator<
    Base : IteratorProtocol
> : IteratorProtocol, Sequence {
    internal var _base: Base
    internal var _count: Int
    
    /// Construct from a `Base` iterator.
    internal init(_base: Base, _offset: Int) {
        self._base = _base
        self._count = _offset
    }
    
    /// The type of element returned by `next()`.
    public typealias Element = (offset: Int, element: Base.Element)
    
    /// Advances to the next element and returns it, or `nil` if no next element
    /// exists.
    ///
    /// Once `nil` has been returned, all subsequent calls return `nil`.
    public mutating func next() -> Element? {
        guard let b = _base.next() else { return nil }
        let result = (offset: _count, element: b)
        _count += 1
        return result
    }
}

public struct EnumeratedSequence<Base : Sequence> : Sequence {
    internal var _base: Base
    internal let _offset: Int
    
    /// Construct from a `Base` sequence.
    internal init(_base: Base, _offset: Int) {
        self._base = _base
        self._offset = _offset
    }
    
    /// Returns an iterator over the elements of this sequence.
    public func makeIterator() -> _EnumeratedIterator<Base.Iterator> {
        return EnumeratedIterator(_base: _base.makeIterator(), _offset: _offset)
    }
}

extension Sequence {
    public func enumerated(from: Int = 0) -> _numeratedSequence<Self> {
        return EnumeratedSequence(_base: self, _offset: from)
    }
}
Source compatibility

Proposed change is source compatible with Swift 3.

Effect on ABI stability and resilience

This change does affect the ABI and should be implemented before we freeze it.

Alternatives considered

Currently proposed workaround for the lack of flexibility in enumerated() is to use zip with the collection and half-open range. From SR-0172 One-sided Ranges:

Additionally, when the index is a countable type, i... should form a Sequence that counts up from i indefinitely. This is useful in forming variants of Sequence.enumerated() when you either want them non-zero-based i.e.zip(1..., greeting), or want to flip the order i.e. zip(greeting, 0...).
Drawback of this approach is that you need to use free function zip, forcing a break in the chain of sequence operations, as there is currently no zipped method on Sequence.

If this is the preffered approach, we should consider removing the enumerated() method altogether, because the limited usefullness in its current state hardly justifies the space on API surface it occupies.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(BJ Homer) #3

Neither of those does what the proposed enumerated(from:) does, if I understand correctly:

let a = [1, 2, 3, 4, 5]
let result = a.enumerated().dropFirst(2)
/*
(offset: 2, element: 3)
(offset: 3, element: 4)
(offset: 4, element: 5)

*/

let result2 = a.dropFirst(2).enumerated()
/*
(offset: 0, element: 3)
(offset: 1, element: 4)
(offset: 2, element: 5)
*/

let proposed = a.enumerated(from: 2)
/*
(offset: 2, element: 1)
(offset: 3, element: 2)
(offset: 4, element: 3)
(offset: 5, element: 4)
(offset: 6, element: 5)
*/
The enumerated(from:) name is not clear; it reads (to me) like it’s going to enumerate elements starting at the Nth element. What it actually does (as proposed) is start counting at N, instead of counting at 0.

I’m not convinced this is a valuable addition to the language. The use cases are not widely applicable, and if you need it it’s easy to get the same results in a way that avoids the confusion.

let a = [1, 2, 3, 4, 5]

let result = a.enumerated().map { (idx, element) in
    return (idx+2, element)
}

// OR

for (idx, element) in a.enumerated() {
    let offsetIndex = idx + 2
    // Use offsetIndex how you will
}

-BJ

···

On May 4, 2017, at 8:51 AM, André Videla via swift-evolution <swift-evolution@swift.org> wrote:

You can do this trivially with drop. But enumerated from has one nice property:

myArray.enumerated().dropFirst(6) // counts from 6

myArray.dropFirst(6).enumerated() // counts from 0

Those two lines do very different things even though they look similar


(Jaden Geller) #4

It's been suggested by a core team member that enumerated itself might not hold its weight in the standard library. Instead, we ought to write `zip(foo.indices, foo)` or `zip(0..., foo)`. It's easier for the caller to see which side of the tuple stores the index and whether the index is an integer or an actual index type. This use case seems to further support this design since `zip(x…, foo)` and `zip(foo.indicies.drop(x), foo)` can be easily written.

···

On May 4, 2017, at 8:50 AM, BJ Homer via swift-evolution <swift-evolution@swift.org> wrote:

On May 4, 2017, at 8:51 AM, André Videla via swift-evolution <swift-evolution@swift.org> wrote:

You can do this trivially with drop. But enumerated from has one nice property:

myArray.enumerated().dropFirst(6) // counts from 6

myArray.dropFirst(6).enumerated() // counts from 0

Those two lines do very different things even though they look similar

Neither of those does what the proposed enumerated(from:) does, if I understand correctly:

let a = [1, 2, 3, 4, 5]
let result = a.enumerated().dropFirst(2)
/*
(offset: 2, element: 3)
(offset: 3, element: 4)
(offset: 4, element: 5)

*/

let result2 = a.dropFirst(2).enumerated()
/*
(offset: 0, element: 3)
(offset: 1, element: 4)
(offset: 2, element: 5)
*/

let proposed = a.enumerated(from: 2)
/*
(offset: 2, element: 1)
(offset: 3, element: 2)
(offset: 4, element: 3)
(offset: 5, element: 4)
(offset: 6, element: 5)
*/
The enumerated(from:) name is not clear; it reads (to me) like it’s going to enumerate elements starting at the Nth element. What it actually does (as proposed) is start counting at N, instead of counting at 0.

I’m not convinced this is a valuable addition to the language. The use cases are not widely applicable, and if you need it it’s easy to get the same results in a way that avoids the confusion.

let a = [1, 2, 3, 4, 5]

let result = a.enumerated().map { (idx, element) in
    return (idx+2, element)
}

// OR

for (idx, element) in a.enumerated() {
    let offsetIndex = idx + 2
    // Use offsetIndex how you will
}

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


(Ben Cohen) #5

It's been suggested by a core team member that enumerated itself might not hold its weight in the standard library. Instead, we ought to write `zip(foo.indices, foo)` or `zip(0..., foo)`. It's easier for the caller to see which side of the tuple stores the index and whether the index is an integer or an actual index type. This use case seems to further support this design since `zip(x…, foo)` and `zip(foo.indicies.drop(x), foo)` can be easily written.

Yup, this is my personal preference. Enumerated has a number of correctness and usability issues: slices aren’t zero based so you could miss a potential trap at runtime, order of the two arguments is unclear unlike the zip alternative, and it encourages people to write code using integers rather than indices even one the latter is more expressive and generalizes better if you ever want to make what you wrote generic.

Note also, since SE-0172 is now implemented on master, you can also write zip(6…, myArray) if you want to alter the starting value.

···

On May 4, 2017, at 10:15 AM, Jaden Geller via swift-evolution <swift-evolution@swift.org> wrote:

On May 4, 2017, at 8:50 AM, BJ Homer via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On May 4, 2017, at 8:51 AM, André Videla via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

You can do this trivially with drop. But enumerated from has one nice property:

myArray.enumerated().dropFirst(6) // counts from 6

myArray.dropFirst(6).enumerated() // counts from 0

Those two lines do very different things even though they look similar

Neither of those does what the proposed enumerated(from:) does, if I understand correctly:

let a = [1, 2, 3, 4, 5]
let result = a.enumerated().dropFirst(2)
/*
(offset: 2, element: 3)
(offset: 3, element: 4)
(offset: 4, element: 5)

*/

let result2 = a.dropFirst(2).enumerated()
/*
(offset: 0, element: 3)
(offset: 1, element: 4)
(offset: 2, element: 5)
*/

let proposed = a.enumerated(from: 2)
/*
(offset: 2, element: 1)
(offset: 3, element: 2)
(offset: 4, element: 3)
(offset: 5, element: 4)
(offset: 6, element: 5)
*/
The enumerated(from:) name is not clear; it reads (to me) like it’s going to enumerate elements starting at the Nth element. What it actually does (as proposed) is start counting at N, instead of counting at 0.

I’m not convinced this is a valuable addition to the language. The use cases are not widely applicable, and if you need it it’s easy to get the same results in a way that avoids the confusion.

let a = [1, 2, 3, 4, 5]

let result = a.enumerated().map { (idx, element) in
    return (idx+2, element)
}

// OR

for (idx, element) in a.enumerated() {
    let offsetIndex = idx + 2
    // Use offsetIndex how you will
}

-BJ
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Pavol Vaskovic) #6

(I forgot to reply-all to mailing list, sorry for double posting Ben!)

It's been suggested by a core team member that enumerated itself might not
hold its weight in the standard library. Instead, we ought to write
`zip(foo.indices, foo)` or `zip(0..., foo)`. It's easier for the caller to
see which side of the tuple stores the index and whether the index is an
integer or an actual index type. This use case seems to further support
this design since `zip(x…, foo)` and `zip(foo.indicies.drop(x), foo)` can
be easily written.

Yup, this is my personal preference. Enumerated has a number of
correctness and usability issues: slices aren’t zero based so you could
miss a potential trap at runtime, order of the two arguments is unclear
unlike the zip alternative, and it encourages people to write code using
integers rather than indices even one the latter is more expressive and
generalizes better if you ever want to make what you wrote generic.

Note also, since SE-0172 is now implemented on master, you can also write
zip(6…, myArray) if you want to alter the starting value.

I have mentioned this in Alternatives considered section, with following
comment:

Drawback of this approach is that you need to use free function zip,

forcing a break in the chain of sequence operations, as there is currently
no zipped method on Sequence.

If this is the preffered approach, we should consider removing the

enumerated() method altogether, because the limited usefullness in its
current state hardly justifies the space on API surface it occupies.

I'd be personally fine with removal of enumerated() if we added
zipped(with:).
Free function zip is essential, no doubt. What was the reason there isn't
also zipped, Ben?

--Pavol

···

On Thu, May 4, 2017 at 8:11 PM, Ben Cohen via swift-evolution < swift-evolution@swift.org> wrote:

On May 4, 2017, at 10:15 AM, Jaden Geller via swift-evolution < > swift-evolution@swift.org> wrote:


(Xiaodi Wu) #7

Sorry, I'm confused: what is the point of adding a method that does the
same thing as an existing free function? With one-sided ranges now a part
of the language, I'd support removal of `enumerated()` with no other
changes.

···

On Sun, May 7, 2017 at 2:23 AM, Pavol Vaskovic via swift-evolution < swift-evolution@swift.org> wrote:

(I forgot to reply-all to mailing list, sorry for double posting Ben!)

On Thu, May 4, 2017 at 8:11 PM, Ben Cohen via swift-evolution < > swift-evolution@swift.org> wrote:

On May 4, 2017, at 10:15 AM, Jaden Geller via swift-evolution < >> swift-evolution@swift.org> wrote:

It's been suggested by a core team member that enumerated itself might
not hold its weight in the standard library. Instead, we ought to write
`zip(foo.indices, foo)` or `zip(0..., foo)`. It's easier for the caller to
see which side of the tuple stores the index and whether the index is an
integer or an actual index type. This use case seems to further support
this design since `zip(x…, foo)` and `zip(foo.indicies.drop(x), foo)` can
be easily written.

Yup, this is my personal preference. Enumerated has a number of
correctness and usability issues: slices aren’t zero based so you could
miss a potential trap at runtime, order of the two arguments is unclear
unlike the zip alternative, and it encourages people to write code using
integers rather than indices even one the latter is more expressive and
generalizes better if you ever want to make what you wrote generic.

Note also, since SE-0172 is now implemented on master, you can also write
zip(6…, myArray) if you want to alter the starting value.

I have mentioned this in Alternatives considered section, with following
comment:

> Drawback of this approach is that you need to use free function zip,
forcing a break in the chain of sequence operations, as there is currently
no zipped method on Sequence.

> If this is the preffered approach, we should consider removing the
enumerated() method altogether, because the limited usefullness in its
current state hardly justifies the space on API surface it occupies.

I'd be personally fine with removal of enumerated() if we added
zipped(with:).
Free function zip is essential, no doubt. What was the reason there isn't
also zipped, Ben?


(Pavol Vaskovic) #8

I’m talking about scenario where you have a chain of sequence operations, say:

(1...N).makeIterator().enumerated().lazy.prefix(while: {$0.0 < oneLess}).count()

With a free function, you need to break the chain in order to replace enumerated with zip:

zip((1...N).makeIterator(), 0...oneLess).lazy.prefix(while: {$0.0 < oneLess}).count()

It forces you to rearrange your code into less than ideal order. Free function zip works great if you start with it. Not when you need to employ it in the middle of the chain.

(1...N).makeIterator().zipped(with: 0...oneLess).lazy.prefix(while: {$0.0 < oneLess}).count()

Just to be clear, how much change I’m proposing here, in case we remove enumerated:

extension Sequence {
    func zipped<S>(with otherSequence: S) -> Zip2Sequence<Self, S> where S: Sequence {
        return zip (self, otherSequence)
    }
}

Best regards
Pavol Vaskovic

···

On 7 May 2017, at 10:30, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Sorry, I'm confused: what is the point of adding a method that does the same thing as an existing free function? With one-sided ranges now a part of the language, I'd support removal of `enumerated()` with no other changes.


(Dave Abrahams) #9

That is my preferred outcome.

···

on Sun May 07 2017, Pavol Vaskovic <swift-evolution@swift.org> wrote:

(I forgot to reply-all to mailing list, sorry for double posting Ben!)

On Thu, May 4, 2017 at 8:11 PM, Ben Cohen via swift-evolution < > swift-evolution@swift.org> wrote:

On May 4, 2017, at 10:15 AM, Jaden Geller via swift-evolution < >> swift-evolution@swift.org> wrote:

It's been suggested by a core team member that enumerated itself might not
hold its weight in the standard library. Instead, we ought to write
`zip(foo.indices, foo)` or `zip(0..., foo)`. It's easier for the caller to
see which side of the tuple stores the index and whether the index is an
integer or an actual index type. This use case seems to further support
this design since `zip(x…, foo)` and `zip(foo.indicies.drop(x), foo)` can
be easily written.

Yup, this is my personal preference. Enumerated has a number of
correctness and usability issues: slices aren’t zero based so you could
miss a potential trap at runtime, order of the two arguments is unclear
unlike the zip alternative, and it encourages people to write code using
integers rather than indices even one the latter is more expressive and
generalizes better if you ever want to make what you wrote generic.

Note also, since SE-0172 is now implemented on master, you can also write
zip(6…, myArray) if you want to alter the starting value.

I have mentioned this in Alternatives considered section, with following
comment:

Drawback of this approach is that you need to use free function zip,

forcing a break in the chain of sequence operations, as there is currently
no zipped method on Sequence.

If this is the preffered approach, we should consider removing the

enumerated() method altogether, because the limited usefullness in its
current state hardly justifies the space on API surface it occupies.

--
-Dave


(Xiaodi Wu) #10

I don't see where anything is broken. It sounds like you simply prefer one
style over another, which is fine but cannot justify two features in the
standard library that do the same thing. As you demonstrate, you can easily
write your own method if that's your preference.

···

On Sun, May 7, 2017 at 04:18 Pavol Vaskovic <pali@pali.sk> wrote:

On 7 May 2017, at 10:30, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Sorry, I'm confused: what is the point of adding a method that does the
same thing as an existing free function? With one-sided ranges now a part
of the language, I'd support removal of `enumerated()` with no other
changes.

I’m talking about scenario where you have a chain of sequence operations,
say:

(1...N).makeIterator().enumerated().lazy.prefix(while: {$0.0 < oneLess}).
count()

With a free function, you need to break the chain in order to replace
enumerated with zip:

zip((1...N).makeIterator(), 0...oneLess).lazy.prefix(while: {$0.0 <
oneLess}).count()

It forces you to rearrange your code into less than ideal order. Free
function zip works great if you start with it. Not when you need to employ
it in the middle of the chain.

(1...N).makeIterator().zipped(with: 0...oneLess).lazy.prefix(while: {$0.0
< oneLess}).count()

Just to be clear, how much change I’m proposing here, in case we remove
enumerated:

extension Sequence {
    func zipped<S>(with otherSequence: S) -> Zip2Sequence<Self, S> where S:
Sequence {
        return zip (self, otherSequence)
    }
}

Best regards
Pavol Vaskovic


(Pavol Vaskovic) #11

All right, so if I understand this correctly a SE to remove enumerated wold
be in order.

I see following open questions:
* migration path to using zip + open range (can migrator handle it without
introducing `zipped`?)
* performance impact (does zip + open range perform comparably to
`EnumeratedSequence`?)

Am I forgetting something?

--Pavol

···

On Fri, May 12, 2017 at 9:09 AM, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

on Sun May 07 2017, Pavol Vaskovic <swift-evolution@swift.org> wrote:

>> Drawback of this approach is that you need to use free function zip,
> forcing a break in the chain of sequence operations, as there is
currently
> no zipped method on Sequence.
>
>> If this is the preffered approach, we should consider removing the
> enumerated() method altogether, because the limited usefullness in its
> current state hardly justifies the space on API surface it occupies.

That is my preferred outcome.


(Ben Cohen) #12

I agree we shouldn’t have both, that would cause confusion/bloat. The downside I see of making zip a method on Sequence is that the first argument is not somehow more “special” than the second. Were it not for the chaining (and discoverability) issue, I’d be against it – it feels right as a free function. By the same rationale, would max be a method on Comparable?

The bouncing-right-to-left issue is a more general problem, and is also a problem with feeding results into initializers. An alternative is that someday we could add the pipe-forward operator:

(note this is not a pitch, just a someday-idea :slight_smile:

// precedence would need some thought
infix operator |>

func |> <T,U>(lhs: T, rhs: (T)->U) -> U {
    return rhs(lhs)
}

let words = ["five","four","three","two","one","blastoff!"]
((0...5).reversed() |> { zip($0, words) })
    .forEach { print($0.0,$0.1, separator: ": ") }

Still pretty ugly though. Some language sugar (again not a pitch) might help:

((0...5).reversed() |> zip(_, words))
    .forEach { print($0.0,$0.1, separator: ": ") }

Then again, I don’t know if facilitating chaining like this is really that important. I usually find assigning to an intermediate variable to be just as readable when the chaining doesn’t quite flow right.

···

On May 7, 2017, at 11:01 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

I don't see where anything is broken. It sounds like you simply prefer one style over another, which is fine but cannot justify two features in the standard library that do the same thing. As you demonstrate, you can easily write your own method if that's your preference.
On Sun, May 7, 2017 at 04:18 Pavol Vaskovic <pali@pali.sk <mailto:pali@pali.sk>> wrote:

On 7 May 2017, at 10:30, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

Sorry, I'm confused: what is the point of adding a method that does the same thing as an existing free function? With one-sided ranges now a part of the language, I'd support removal of `enumerated()` with no other changes.

I’m talking about scenario where you have a chain of sequence operations, say:

(1...N).makeIterator().enumerated().lazy.prefix(while: {$0.0 < oneLess}).count()

With a free function, you need to break the chain in order to replace enumerated with zip:

zip((1...N).makeIterator(), 0...oneLess).lazy.prefix(while: {$0.0 < oneLess}).count()

It forces you to rearrange your code into less than ideal order. Free function zip works great if you start with it. Not when you need to employ it in the middle of the chain.

(1...N).makeIterator().zipped(with: 0...oneLess).lazy.prefix(while: {$0.0 < oneLess}).count()

Just to be clear, how much change I’m proposing here, in case we remove enumerated:

extension Sequence {
    func zipped<S>(with otherSequence: S) -> Zip2Sequence<Self, S> where S: Sequence {
        return zip (self, otherSequence)
    }
}

Best regards
Pavol Vaskovic


(Pavol Vaskovic) #13

OK, so with all that said, what is the consensus? I see following options:

* do nothing - let low utility enumerated be
* enhance `enumerated` to start from user specified point as per my opening
proposal
* remove `enumerated` because of its low utility - source breaking change
while:
** do nothing else - creates complicated migration situation
** add `zipped` method that provides for easy migrator path and improved
discoverability of zip free function

Best regards
Pavol Vaskovic


(Pavol Vaskovic) #14

One more note:

let words = ["five","four","three","two","one","blastoff!"]
((0...5).reversed() |> { zip($0, words) })
    .forEach { print($0.0,$0.1, separator: ": ") }

...

((0...5).reversed() |> zip(_, words))
    .forEach { print($0.0,$0.1, separator: ": ") }

The code above demonstrates that replacing `enumerated` with `zip` gives
you anonymous tuples that need to be accessed with positional properties.

In contrast, the tuple returned from `enumerated` gives you named tuple
(offset: Int, element: Element).

Does this change your opinion when you take into account the sorry state of
tuple handling in Swift 4, that prevents you from writing concise and
readable functional code? See:

SR-4745 for (index, (a, b)) in dict.enumerated() produces an error
https://bugs.swift.org/browse/SR-4745

SR-4738 Can not decompose nested tuple in closure arguments
https://bugs.swift.org/browse/SR-4738

Best regards
Pavol Vaskovic

···

On Sun, May 7, 2017 at 8:51 PM, Ben Cohen <ben_cohen@apple.com> wrote:


(Xiaodi Wu) #15

When this was previously brought up, I believe the consensus was for
removing enumerated and doing nothing else.

···

On Wed, May 10, 2017 at 02:50 Pavol Vaskovic <pali@pali.sk> wrote:

One more note:

On Sun, May 7, 2017 at 8:51 PM, Ben Cohen <ben_cohen@apple.com> wrote:

let words = ["five","four","three","two","one","blastoff!"]
((0...5).reversed() |> { zip($0, words) })
    .forEach { print($0.0,$0.1, separator: ": ") }

...

((0...5).reversed() |> zip(_, words))
    .forEach { print($0.0,$0.1, separator: ": ") }

The code above demonstrates that replacing `enumerated` with `zip` gives
you anonymous tuples that need to be accessed with positional properties.

In contrast, the tuple returned from `enumerated` gives you named tuple
(offset: Int, element: Element).

Does this change your opinion when you take into account the sorry state
of tuple handling in Swift 4, that prevents you from writing concise and
readable functional code? See:

SR-4745 for (index, (a, b)) in dict.enumerated() produces an error
https://bugs.swift.org/browse/SR-4745

SR-4738 Can not decompose nested tuple in closure arguments
https://bugs.swift.org/browse/SR-4738

Best regards
Pavol Vaskovic


(BJ Homer) #16

I don't see the motivation for removing enumerated(). It's a source-breaking change that seems unjustified to me. Sure, enumerated() isn't as flexible as one might like, and you can certainly reproduce its effects with zip(), but I don't think we've demonstrated that enumerated() is actively harmful. Removing it means obsolescing a bunch of training material, blog posts, etc. What do we gain by removing it?

-BJ

···

On May 10, 2017, at 7:24 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

When this was previously brought up, I believe the consensus was for removing enumerated and doing nothing else.

On Wed, May 10, 2017 at 02:50 Pavol Vaskovic <pali@pali.sk> wrote:
One more note:

On Sun, May 7, 2017 at 8:51 PM, Ben Cohen <ben_cohen@apple.com> wrote:

let words = ["five","four","three","two","one","blastoff!"]
((0...5).reversed() |> { zip($0, words) })
    .forEach { print($0.0,$0.1, separator: ": ") }

...

((0...5).reversed() |> zip(_, words))
    .forEach { print($0.0,$0.1, separator: ": ") }

The code above demonstrates that replacing `enumerated` with `zip` gives you anonymous tuples that need to be accessed with positional properties.

In contrast, the tuple returned from `enumerated` gives you named tuple (offset: Int, element: Element).

Does this change your opinion when you take into account the sorry state of tuple handling in Swift 4, that prevents you from writing concise and readable functional code? See:

SR-4745 for (index, (a, b)) in dict.enumerated() produces an error
https://bugs.swift.org/browse/SR-4745

SR-4738 Can not decompose nested tuple in closure arguments
https://bugs.swift.org/browse/SR-4738

Best regards
Pavol Vaskovic

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


(Xiaodi Wu) #17

Without rehashing the previous thread, the primary motivation is that it's
very harmful as it encourages pervasively incorrect use. Namely, people
often mistaken the offset for the index. A quick look through major
open-source Swift projects shows that the offset is almost exclusively used
for subscripting. It happens to work for arrays, but it is not correct for
collections generally, not even for array slices.

···

On Wed, May 10, 2017 at 08:30 BJ Homer <bjhomer@gmail.com> wrote:

I don't see the motivation for removing enumerated(). It's a
source-breaking change that seems unjustified to me. Sure, enumerated()
isn't as flexible as one might like, and you can certainly reproduce its
effects with zip(), but I don't think we've demonstrated that enumerated()
is actively harmful. Removing it means obsolescing a bunch of training
material, blog posts, etc. What do we gain by removing it?

-BJ

On May 10, 2017, at 7:24 AM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

When this was previously brought up, I believe the consensus was for
removing enumerated and doing nothing else.
On Wed, May 10, 2017 at 02:50 Pavol Vaskovic <pali@pali.sk> wrote:

One more note:

On Sun, May 7, 2017 at 8:51 PM, Ben Cohen <ben_cohen@apple.com> wrote:

let words = ["five","four","three","two","one","blastoff!"]
((0...5).reversed() |> { zip($0, words) })
    .forEach { print($0.0,$0.1, separator: ": ") }

...

((0...5).reversed() |> zip(_, words))
    .forEach { print($0.0,$0.1, separator: ": ") }

The code above demonstrates that replacing `enumerated` with `zip` gives
you anonymous tuples that need to be accessed with positional properties.

In contrast, the tuple returned from `enumerated` gives you named tuple
(offset: Int, element: Element).

Does this change your opinion when you take into account the sorry state
of tuple handling in Swift 4, that prevents you from writing concise and
readable functional code? See:

SR-4745 for (index, (a, b)) in dict.enumerated() produces an error
https://bugs.swift.org/browse/SR-4745

SR-4738 Can not decompose nested tuple in closure arguments
https://bugs.swift.org/browse/SR-4738

Best regards
Pavol Vaskovic

_______________________________________________

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