[Draft]: Introducing a striding(by:) method on 3.0 ranges


(Erica Sadun) #1

Draft here: https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can look at here (https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

Proposal: SE-XXXX <https://gist.github.com/erica/a51a981ee0352235204692affa959307/edit>
Author(s): Xiaodi Wu <https://github.com/xwu>, Pyry Jahkola <http://github.com/pyrtsa>, Nate Cook <http://github.com/natecook1000>, Erica Sadun <http://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/a51a981ee0352235204692affa959307#introduction>Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

This proposal was discussed on the Swift Evolution list in the Feature proposal: Range operator with step <http://search.gmane.org/search.php?group=gmane.comp.lang.swift.evolution&query=Feature+proposal%3A+Range+operator+with+step> thread. (Direct link <http://thread.gmane.org/gmane.comp.lang.swift.evolution/12801/focus=13051> to original thread)

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#motivation>Motivation

Updating Range for Swift 3 offers a window of opportunity to simultaneously improve strides.

Under current Swift 3 plans, n.stride(to:/through:, by:) will be replaced with a standalone stride(from:, to:/through:, by:) function. We propose to replace this change with a method on ranges. Using a method reduces overall API surface area compared to free functions.

In its current incarnation, the standalone stride function uses confusing semantics. The current to implementation returns values in [start, end) and will never reach or get to end. The current through implementation returns values in [start, end]. It may never reach end and certainly never goes through that value. Our proposed method introduces simple, expected semantics that can be extended to both countable and continuous ranges, and to open and closed intervals (both half-open and fully-open).

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#detail-design>Detail Design

The striding(by:) method is called on ranges. When used with a positive step size, the count starts from the lower bound. With a negative step size, the count starts from the upper bound. These bounds apply regardless of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible cases should Swift 3 introduce a full complement of open and closed ranges. The syntax for non-canonical range types is not fixed and can be discussed under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]
To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]
We note that striding by 0 should be always be a precondition failure.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#alternatives-considered>Alternatives Considered

During the on-list discussion, we considered various scenarios that took closed/inclusive bounds into account or excluded open bounds for starting values. For example, we might have prohibited scenarios where multiple interpretations of an intended behavior might exist: is (0 ..< 9).striding(by: -2) a precondition failure? We settled on the simplest, most straight-forward implementation involving the fewest compiler warnings and the lowest likelihood of precondition failures. We subscribe to the "Dave Abrahams Philosophy": excessive special casing and warning scenarios more likely indicates bad language design than bad user comprehension.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#future-directions>Future Directions

We intend to follow up with an expanded operator vocabulary that includes fully open ranges (<.<), fully closed ranges (...) and both half open ranges (<.., ..<). These will support the full vocabulary laid out in the Detail Design section.

Upon adoption, the Swift community may consider expanding this approach to collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}
Striding offers a fundamental operation over collections. The consistent approach introduced in this proposal <http://article.gmane.org/gmane.comp.lang.swift.evolution/13936> helps support the extension of stride semantics to collections.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#acknowlegements>Acknowlegements

Thanks, Dave Abrahams, Matthew Judge


(Brent Royal-Gordon) #2

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

I take it that we're taking an incremental approach here, and further steps like:

* Adding new range operators
* Making all collections Strideable
* Unifying Range and IntervalType so we can stride over non-integers again
* Correctly striding over floats

Are out of scope for this particular proposal.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

I favor `stride(over: 0...9, by: 2)`—or perhaps even `Stride(over: 0...9, by: 2)`, where `Stride` is a replacement for `StrideTo` and `StrideThrough`—over a `striding(by:)` method. The precedence of the range-construction operators makes calling methods on a Range unusually difficult and ugly. (And I don't think adjusting the precedence is a realistic option; that would break things like `0..<array.count`.) Passing the Range as a parameter instead evades this issue.

To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]

Does this have different behavior from `(0...9).striding(by: -2)`? (I know it probably has a different implementation.)

···

--
Brent Royal-Gordon
Architechies


(Michel Fortin) #3

The above reads wrong to me. The expression has to be read differently depending on the tinny detail that is the sign of the step that comes last:

* positive step: from 0 to 9 striding by 2
* negative step: to 0 from 9 striding by -2

Am I the only one thinking it's a bit too clever to swap the start and stop parts like this?

···

Le 8 avr. 2016 à 14:37, Erica Sadun via swift-evolution <swift-evolution@swift.org> a écrit :

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]

--
Michel Fortin
https://michelf.ca


(Taras Zakharko) #4

I am agnostic on a .striding() method, but I am strongly agains any suggestions of removing/replacing the global stride() function. The stride function does not pollute the global namespace as it is a universally useful construct (among others, for implementing classical iterating for loops), has its history in contemporary programming (comparable functions in languages like Python, R), and is IMO more readable and clear than a range with a striding method method. Furthermore, a stride is not a range — its a special sequence, while for ranges other rules apply. All in all, I don’t see why stride() and .striding() can’t coexist.

— Taras

P.S. As a side note, I would prefer if the stride() function be renamed sequence() or seq(), but thats just cosmetic personal preference.

···

On 08 Apr 2016, at 20:37, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Draft here: https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can look at here (https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

Proposal: SE-XXXX <https://gist.github.com/erica/a51a981ee0352235204692affa959307/edit>
Author(s): Xiaodi Wu <https://github.com/xwu>, Pyry Jahkola <http://github.com/pyrtsa>, Nate Cook <http://github.com/natecook1000>, Erica Sadun <http://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/a51a981ee0352235204692affa959307#introduction>Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

This proposal was discussed on the Swift Evolution list in the Feature proposal: Range operator with step <http://search.gmane.org/search.php?group=gmane.comp.lang.swift.evolution&query=Feature+proposal%3A+Range+operator+with+step> thread. (Direct link <http://thread.gmane.org/gmane.comp.lang.swift.evolution/12801/focus=13051> to original thread)

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#motivation>Motivation

Updating Range for Swift 3 offers a window of opportunity to simultaneously improve strides.

Under current Swift 3 plans, n.stride(to:/through:, by:) will be replaced with a standalone stride(from:, to:/through:, by:) function. We propose to replace this change with a method on ranges. Using a method reduces overall API surface area compared to free functions.

In its current incarnation, the standalone stride function uses confusing semantics. The current to implementation returns values in [start, end) and will never reach or get to end. The current through implementation returns values in [start, end]. It may never reach end and certainly never goes through that value. Our proposed method introduces simple, expected semantics that can be extended to both countable and continuous ranges, and to open and closed intervals (both half-open and fully-open).

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#detail-design>Detail Design

The striding(by:) method is called on ranges. When used with a positive step size, the count starts from the lower bound. With a negative step size, the count starts from the upper bound. These bounds apply regardless of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible cases should Swift 3 introduce a full complement of open and closed ranges. The syntax for non-canonical range types is not fixed and can be discussed under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]
To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]
We note that striding by 0 should be always be a precondition failure.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#alternatives-considered>Alternatives Considered

During the on-list discussion, we considered various scenarios that took closed/inclusive bounds into account or excluded open bounds for starting values. For example, we might have prohibited scenarios where multiple interpretations of an intended behavior might exist: is (0 ..< 9).striding(by: -2) a precondition failure? We settled on the simplest, most straight-forward implementation involving the fewest compiler warnings and the lowest likelihood of precondition failures. We subscribe to the "Dave Abrahams Philosophy": excessive special casing and warning scenarios more likely indicates bad language design than bad user comprehension.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#future-directions>Future Directions

We intend to follow up with an expanded operator vocabulary that includes fully open ranges (<.<), fully closed ranges (...) and both half open ranges (<.., ..<). These will support the full vocabulary laid out in the Detail Design section.

Upon adoption, the Swift community may consider expanding this approach to collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}
Striding offers a fundamental operation over collections. The consistent approach introduced in this proposal <http://article.gmane.org/gmane.comp.lang.swift.evolution/13936> helps support the extension of stride semantics to collections.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#acknowlegements>Acknowlegements

Thanks, Dave Abrahams, Matthew Judge

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


(Haravikk) #5

While I’m in favour of the basic idea I think the operator selection is too complex, and I’m not sure about the need for negative strides. Really all I want are the following:

  (0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
  (0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
  (6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
  (6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

Everything else should be coverable either by flipping the order, or using .reverse(). The main advantage is that there’s only one new operator to clarify the 6 ..> 0 case, though you could always just reuse the existing operator if you just interpret it as “x from 6 to, but not including, 0"

I dunno, I just don’t think that introducing tons of new operators is going to simplify things, and could lead to way more mistakes in practice; the only mistake above would be putting the indices in the wrong order and accidentally reversing the result.

Also, I’m against negative strides; while they could be useful for convenience (avoid the need for .reverse or flipping values) I’m just not sure that it makes sense. To me a stride is a distance, thus absolute, what matters is the direction of the range. Naturally we’d need ranges with a direction, but with collections requiring it to be in a particular order (or ignoring it and flipping where necessary).

···

On 8 Apr 2016, at 19:37, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Draft here: https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can look at here (https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

Proposal: SE-XXXX <https://gist.github.com/erica/a51a981ee0352235204692affa959307/edit>
Author(s): Xiaodi Wu <https://github.com/xwu>, Pyry Jahkola <http://github.com/pyrtsa>, Nate Cook <http://github.com/natecook1000>, Erica Sadun <http://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/a51a981ee0352235204692affa959307#introduction>Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

This proposal was discussed on the Swift Evolution list in the Feature proposal: Range operator with step <http://search.gmane.org/search.php?group=gmane.comp.lang.swift.evolution&query=Feature+proposal%3A+Range+operator+with+step> thread. (Direct link <http://thread.gmane.org/gmane.comp.lang.swift.evolution/12801/focus=13051> to original thread)

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#motivation>Motivation

Updating Range for Swift 3 offers a window of opportunity to simultaneously improve strides.

Under current Swift 3 plans, n.stride(to:/through:, by:) will be replaced with a standalone stride(from:, to:/through:, by:) function. We propose to replace this change with a method on ranges. Using a method reduces overall API surface area compared to free functions.

In its current incarnation, the standalone stride function uses confusing semantics. The current to implementation returns values in [start, end) and will never reach or get to end. The current through implementation returns values in [start, end]. It may never reach end and certainly never goes through that value. Our proposed method introduces simple, expected semantics that can be extended to both countable and continuous ranges, and to open and closed intervals (both half-open and fully-open).

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#detail-design>Detail Design

The striding(by:) method is called on ranges. When used with a positive step size, the count starts from the lower bound. With a negative step size, the count starts from the upper bound. These bounds apply regardless of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible cases should Swift 3 introduce a full complement of open and closed ranges. The syntax for non-canonical range types is not fixed and can be discussed under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]
To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]
We note that striding by 0 should be always be a precondition failure.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#alternatives-considered>Alternatives Considered

During the on-list discussion, we considered various scenarios that took closed/inclusive bounds into account or excluded open bounds for starting values. For example, we might have prohibited scenarios where multiple interpretations of an intended behavior might exist: is (0 ..< 9).striding(by: -2) a precondition failure? We settled on the simplest, most straight-forward implementation involving the fewest compiler warnings and the lowest likelihood of precondition failures. We subscribe to the "Dave Abrahams Philosophy": excessive special casing and warning scenarios more likely indicates bad language design than bad user comprehension.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#future-directions>Future Directions

We intend to follow up with an expanded operator vocabulary that includes fully open ranges (<.<), fully closed ranges (...) and both half open ranges (<.., ..<). These will support the full vocabulary laid out in the Detail Design section.

Upon adoption, the Swift community may consider expanding this approach to collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}
Striding offers a fundamental operation over collections. The consistent approach introduced in this proposal <http://article.gmane.org/gmane.comp.lang.swift.evolution/13936> helps support the extension of stride semantics to collections.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#acknowlegements>Acknowlegements

Thanks, Dave Abrahams, Matthew Judge

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


(Ben Rimmington) #6

Re: "Completing the Swift 3 range operator suite"
<https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9>

The proposed operators (<.. and <.<) are not allowed by Swift 2.2 lexer grammar:

<https://github.com/apple/swift/commit/0bfacde2420937bfb6e0e1be6567b0e90ee2fb67>
<https://github.com/apple/swift/commit/d0256a50d0346f7d4b4c0054dddd0734febc6bb1>

-- Ben


(Wallacy) #7

Just as note, i think the sintax should be:

0...9
0..<9
0>..9
0>.<9

Because the intention is produce a number bigger than 0 (start). So greater
than zero less than nine.

···

Em sex, 8 de abr de 2016 22:50, Michel Fortin via swift-evolution < swift-evolution@swift.org> escreveu:

Le 8 avr. 2016 à 14:37, Erica Sadun via swift-evolution < > swift-evolution@swift.org> a écrit :
>
> (0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]

The above reads wrong to me. The expression has to be read differently
depending on the tinny detail that is the sign of the step that comes last:

* positive step: from 0 to 9 striding by 2
* negative step: to 0 from 9 striding by -2

Am I the only one thinking it's a bit too clever to swap the start and
stop parts like this?

--
Michel Fortin
https://michelf.ca

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


(Xiaodi Wu) #8

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

I take it that we're taking an incremental approach here,

Yes, other proposals are on their way to complement this one.

and further steps like:

* Adding new range operators

There's a draft proposal being worked on here:
https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9
We're still working on text to give more details on how to implement,
stronger use cases, etc.

* Making all collections Strideable

This merits additional discussion as to what it means to stride.

* Unifying Range and IntervalType so we can stride over non-integers again

This would be Dave A's work, which is nearing completion but hasn't
yet been reviewed. You can inspect the ongoing work in the
swift-3-indexing-model branch.

* Correctly striding over floats

Conflicting opinions exist as to whether to roll that into this
proposal or to write a separate one on that topic.

Are out of scope for this particular proposal.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

I favor `stride(over: 0...9, by: 2)`—or perhaps even `Stride(over: 0...9, by: 2)`, where `Stride` is a replacement for `StrideTo` and `StrideThrough`—over a `striding(by:)` method. The precedence of the range-construction operators makes calling methods on a Range unusually difficult and ugly. (And I don't think adjusting the precedence is a realistic option; that would break things like `0..<array.count`.) Passing the Range as a parameter instead evades this issue.

This is an interesting suggestion. One argument for abandoning the
stride(from:to:by:) notation was that what "to" or "through" meant
could be misconstrued. This neatly solves that issue. There is still a
preference for methods over free functions, though.

To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]

Does this have different behavior from `(0...9).striding(by: -2)`? (I know it probably has a different implementation.)

It does. We propose `(0...9).striding(by: -2) == [9, 7, 5, 3, 1]`.

···

On Sat, Apr 9, 2016 at 1:00 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

--
Brent Royal-Gordon
Architechies


(Xiaodi Wu) #9

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]

The above reads wrong to me. The expression has to be read differently depending on the tinny detail that is the sign of the step that comes last:

* positive step: from 0 to 9 striding by 2
* negative step: to 0 from 9 striding by -2

Am I the only one thinking it's a bit too clever to swap the start and stop parts like this?

The issue here is that ranges both do and don't have a direction.
0...9 is supposed to represent all numbers between and including 0 and
9 (agnostic of direction). However, when you have `for i in 0...9 {
code }`, the sequence very much goes from 0 through 9 and not the
other way round.

Note that it's not possible to write 9...0. Once that is clear, it's
fairly intuitive (IMO) to look to the stride size for indicating the
direction in which we stride, since that is the only other value there
is. Thus, we propose using a negative stride size means stride
backwards through the range.

···

On Sat, Apr 9, 2016 at 2:49 AM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 8 avr. 2016 à 14:37, Erica Sadun via swift-evolution <swift-evolution@swift.org> a écrit :

--
Michel Fortin
https://michelf.ca


(Xiaodi Wu) #10

I agree with 90% of your comment.

But, ranges are useful for more than just stride, and so I would say it
doesn't make sense to have an intrinsic direction for Range, which
pointedly has lowerBound and upperBound but not start and end. For sample,
there shouldn't be (0...9).contains(1) and (9...0).contains(1).

It's the *traversal* of the range by a stride that has a direction, and
thus the direction should be specified for a particular stride.

Similarly, the rationale for completing the range operators is for the uses
of range that include but are not limited to just stride. If it were just
for stride, then four range operators is three too many.

···

On Sat, Apr 9, 2016 at 10:33 AM Haravikk <swift-evolution@haravikk.me> wrote:

While I’m in favour of the basic idea I think the operator selection is
too complex, and I’m not sure about the need for negative strides. Really
all I want are the following:

(0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
(0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
(6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
(6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

Everything else should be coverable either by flipping the order, or using
.reverse(). The main advantage is that there’s only one new operator to
clarify the 6 ..> 0 case, though you could always just reuse the existing
operator if you just interpret it as “x from 6 to, but not including, 0"

I dunno, I just don’t think that introducing tons of new operators is
going to simplify things, and could lead to way more mistakes in practice;
the only mistake above would be putting the indices in the wrong order and
accidentally reversing the result.

Also, I’m against negative strides; while they could be useful for
convenience (avoid the need for .reverse or flipping values) I’m just not
sure that it makes sense. To me a stride is a distance, thus absolute, what
matters is the direction of the range. Naturally we’d need ranges with a
direction, but with collections requiring it to be in a particular order
(or ignoring it and flipping where necessary).

On 8 Apr 2016, at 19:37, Erica Sadun via swift-evolution < > swift-evolution@swift.org> wrote:

Draft here: https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback
solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can
look at here (
https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

   - Proposal: SE-XXXX
   <https://gist.github.com/erica/a51a981ee0352235204692affa959307/edit>
   - Author(s): Xiaodi Wu <https://github.com/xwu>, Pyry Jahkola
   <http://github.com/pyrtsa>, Nate Cook <http://github.com/natecook1000>
   , Erica Sadun <http://github.com/erica>
   - Status: TBD
   - Review manager: TBD

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#introduction>
Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range
type.

This proposal was discussed on the Swift Evolution list in the Feature
proposal: Range operator with step
<http://search.gmane.org/search.php?group=gmane.comp.lang.swift.evolution&query=Feature+proposal%3A+Range+operator+with+step> thread.
(Direct link
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/12801/focus=13051> to
original thread)
<https://gist.github.com/erica/a51a981ee0352235204692affa959307#motivation>
Motivation

Updating Range for Swift 3 offers a window of opportunity to
simultaneously improve strides.

   -

   Under current Swift 3 plans, n.stride(to:/through:, by:) will be
   replaced with a standalone stride(from:, to:/through:, by:) function.
   We propose to replace this change with a method on ranges. Using a method
   reduces overall API surface area compared to free functions.
   -

   In its current incarnation, the standalone stride function uses
   confusing semantics. The current to implementation returns values in
   *[start, end)* and will never reach or get *to* end. The current
   through implementation returns values in *[start, end]*. It may never
   reach end and certainly never goes *through* that value. Our proposed
   method introduces simple, expected semantics that can be extended to both
   countable and continuous ranges, and to open and closed intervals (both
   half-open and fully-open).

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#detail-design>Detail
Design

The striding(by:) method is called on ranges. When used with a positive
step size, the count starts from the lower bound. With a negative step
size, the count starts from the upper bound. These bounds apply regardless
of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible
cases should Swift 3 introduce a full complement of open and closed ranges.
The syntax for non-canonical range types is not fixed and can be discussed
under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]

To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]

We note that striding by 0 should be always be a precondition failure.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#alternatives-considered>Alternatives
Considered

During the on-list discussion, we considered various scenarios that took
closed/inclusive bounds into account or excluded open bounds for starting
values. For example, we might have prohibited scenarios where multiple
interpretations of an intended behavior might exist: is (0 ..<
9).striding(by: -2) a precondition failure? We settled on the simplest,
most straight-forward implementation involving the fewest compiler warnings
and the lowest likelihood of precondition failures. We subscribe to the
"Dave Abrahams Philosophy": excessive special casing and warning scenarios
more likely indicates bad language design than bad user comprehension.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#future-directions>Future
Directions

We intend to follow up with an expanded operator vocabulary that includes
fully open ranges (<.<), fully closed ranges (...) and both half open
ranges (<.., ..<). These will support the full vocabulary laid out in the
Detail Design section.

Upon adoption, the Swift community may consider expanding this approach to
collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}

Striding offers a fundamental operation over collections. The consistent
approach introduced in this proposal
<http://article.gmane.org/gmane.comp.lang.swift.evolution/13936> helps
support the extension of stride semantics to collections.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#acknowlegements>
Acknowlegements
Thanks, Dave Abrahams, Matthew Judge

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


(Xiaodi Wu) #11

Indeed. It's noted under "design concerns" and, IMO, raises the bar for
their addition.

···

On Sat, Apr 9, 2016 at 12:05 PM Ben Rimmington via swift-evolution < swift-evolution@swift.org> wrote:

Re: "Completing the Swift 3 range operator suite"
<https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9>

The proposed operators (<.. and <.<) are not allowed by Swift 2.2 lexer
grammar:

<
https://github.com/apple/swift/commit/0bfacde2420937bfb6e0e1be6567b0e90ee2fb67
>
<
https://github.com/apple/swift/commit/d0256a50d0346f7d4b4c0054dddd0734febc6bb1
>

-- Ben

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


(David Sweeris) #12

`.reverse()` returns an array, though, not a StrideTo<>, which means it’ll get in an infinite loop on infinite sequences. This works fine:
for i in stride(from: 0.0, to: Double.infinity, by: M_PI) {
    if someTestInvolving(i) { break }
    ...
}

But this never even starts executing the loop because of the infinite loop inside `.reverse()`:
for i in stride(from: -Double.infinity, to: 0.0, by: M_PI).reverse() {
    if someTestInvolving(i) { break }
    ...
}

- Dave Sweeris

···

On Apr 9, 2016, at 4:33 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

While I’m in favour of the basic idea I think the operator selection is too complex, and I’m not sure about the need for negative strides. Really all I want are the following:

  (0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
  (0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
  (6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
  (6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

Everything else should be coverable either by flipping the order, or using .reverse(). The main advantage is that there’s only one new operator to clarify the 6 ..> 0 case, though you could always just reuse the existing operator if you just interpret it as “x from 6 to, but not including, 0"


(Haravikk) #13

But, ranges are useful for more than just stride, and so I would say it doesn't make sense to have an intrinsic direction for Range, which pointedly has lowerBound and upperBound but not start and end. For sample, there shouldn't be (0...9).contains(1) and (9...0).contains(1).

While I can appreciate that, I wonder if it’s really that important that 0…9 and 9…0 are identical? As long as we can be clear on which direction of range we want to generate (so probably still need two new operators), can tell what the direction is, and can convert between them if we need to for some reason (reverse should be fine for that?), then I think we’re okay.

i.e- 9 … 0 would still cause an error at compile or run-time, we’d have some other operator for doing that, not sure what, plus 9 ..> 0, with both explicitly creating ranges in the reverse direction to avoid mistakes with the order or for computed indices. When it comes down to it the current Range has an implicitly forward direction, so I don’t see the problem with having the same in reverse personally.

I dunno, at the very least we might want to consider overloading the striding method as .striding(forwardBy:) and .striding(backwardBy:) or something similar, each taking a positive value of some kind to help avoid mistakes in cases where the stride size is computed rather than constant, this would make it more explicit at least.

···

On 9 Apr 2016, at 10:50, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Apr 9, 2016 at 10:33 AM Haravikk <swift-evolution@haravikk.me <mailto:swift-evolution@haravikk.me>> wrote:
While I’m in favour of the basic idea I think the operator selection is too complex, and I’m not sure about the need for negative strides. Really all I want are the following:

  (0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
  (0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
  (6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
  (6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

Everything else should be coverable either by flipping the order, or using .reverse(). The main advantage is that there’s only one new operator to clarify the 6 ..> 0 case, though you could always just reuse the existing operator if you just interpret it as “x from 6 to, but not including, 0"

I dunno, I just don’t think that introducing tons of new operators is going to simplify things, and could lead to way more mistakes in practice; the only mistake above would be putting the indices in the wrong order and accidentally reversing the result.

Also, I’m against negative strides; while they could be useful for convenience (avoid the need for .reverse or flipping values) I’m just not sure that it makes sense. To me a stride is a distance, thus absolute, what matters is the direction of the range. Naturally we’d need ranges with a direction, but with collections requiring it to be in a particular order (or ignoring it and flipping where necessary).

On 8 Apr 2016, at 19:37, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Draft here: https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can look at here (https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

Proposal: SE-XXXX <https://gist.github.com/erica/a51a981ee0352235204692affa959307/edit>
Author(s): Xiaodi Wu <https://github.com/xwu>, Pyry Jahkola <http://github.com/pyrtsa>, Nate Cook <http://github.com/natecook1000>, Erica Sadun <http://github.com/erica>
Status: TBD
Review manager: TBD
<https://gist.github.com/erica/a51a981ee0352235204692affa959307#introduction>Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

This proposal was discussed on the Swift Evolution list in the Feature proposal: Range operator with step <http://search.gmane.org/search.php?group=gmane.comp.lang.swift.evolution&query=Feature+proposal%3A+Range+operator+with+step> thread. (Direct link <http://thread.gmane.org/gmane.comp.lang.swift.evolution/12801/focus=13051> to original thread)

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#motivation>Motivation

Updating Range for Swift 3 offers a window of opportunity to simultaneously improve strides.

Under current Swift 3 plans, n.stride(to:/through:, by:) will be replaced with a standalone stride(from:, to:/through:, by:) function. We propose to replace this change with a method on ranges. Using a method reduces overall API surface area compared to free functions.

In its current incarnation, the standalone stride function uses confusing semantics. The current to implementation returns values in [start, end) and will never reach or get to end. The current through implementation returns values in [start, end]. It may never reach end and certainly never goes through that value. Our proposed method introduces simple, expected semantics that can be extended to both countable and continuous ranges, and to open and closed intervals (both half-open and fully-open).

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#detail-design>Detail Design

The striding(by:) method is called on ranges. When used with a positive step size, the count starts from the lower bound. With a negative step size, the count starts from the upper bound. These bounds apply regardless of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible cases should Swift 3 introduce a full complement of open and closed ranges. The syntax for non-canonical range types is not fixed and can be discussed under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]
To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]
We note that striding by 0 should be always be a precondition failure.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#alternatives-considered>Alternatives Considered

During the on-list discussion, we considered various scenarios that took closed/inclusive bounds into account or excluded open bounds for starting values. For example, we might have prohibited scenarios where multiple interpretations of an intended behavior might exist: is (0 ..< 9).striding(by: -2) a precondition failure? We settled on the simplest, most straight-forward implementation involving the fewest compiler warnings and the lowest likelihood of precondition failures. We subscribe to the "Dave Abrahams Philosophy": excessive special casing and warning scenarios more likely indicates bad language design than bad user comprehension.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#future-directions>Future Directions

We intend to follow up with an expanded operator vocabulary that includes fully open ranges (<.<), fully closed ranges (...) and both half open ranges (<.., ..<). These will support the full vocabulary laid out in the Detail Design section.

Upon adoption, the Swift community may consider expanding this approach to collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}
Striding offers a fundamental operation over collections. The consistent approach introduced in this proposal <http://article.gmane.org/gmane.comp.lang.swift.evolution/13936> helps support the extension of stride semantics to collections.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#acknowlegements>Acknowlegements

Thanks, Dave Abrahams, Matthew Judge

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


(Thorsten Seitz) #14

I agree with Taras that stride() and .striding() should coexist.

-Thorsten

···

Am 09.04.2016 um 01:01 schrieb Taras Zakharko via swift-evolution <swift-evolution@swift.org>:

I am agnostic on a .striding() method, but I am strongly agains any suggestions of removing/replacing the global stride() function. The stride function does not pollute the global namespace as it is a universally useful construct (among others, for implementing classical iterating for loops), has its history in contemporary programming (comparable functions in languages like Python, R), and is IMO more readable and clear than a range with a striding method method. Furthermore, a stride is not a range — its a special sequence, while for ranges other rules apply. All in all, I don’t see why stride() and .striding() can’t coexist.

— Taras

P.S. As a side note, I would prefer if the stride() function be renamed sequence() or seq(), but thats just cosmetic personal preference.

On 08 Apr 2016, at 20:37, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Draft here: https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can look at here (https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

Proposal: SE-XXXX
Author(s): Xiaodi Wu, Pyry Jahkola, Nate Cook, Erica Sadun
Status: TBD
Review manager: TBD
Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range type.

This proposal was discussed on the Swift Evolution list in the Feature proposal: Range operator with step thread. (Direct link to original thread)

Motivation

Updating Range for Swift 3 offers a window of opportunity to simultaneously improve strides.

Under current Swift 3 plans, n.stride(to:/through:, by:) will be replaced with a standalone stride(from:, to:/through:, by:) function. We propose to replace this change with a method on ranges. Using a method reduces overall API surface area compared to free functions.

In its current incarnation, the standalone stride function uses confusing semantics. The current to implementation returns values in [start, end) and will never reach or get to end. The current through implementation returns values in [start, end]. It may never reach end and certainly never goes through that value. Our proposed method introduces simple, expected semantics that can be extended to both countable and continuous ranges, and to open and closed intervals (both half-open and fully-open).

Detail Design

The striding(by:) method is called on ranges. When used with a positive step size, the count starts from the lower bound. With a negative step size, the count starts from the upper bound. These bounds apply regardless of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible cases should Swift 3 introduce a full complement of open and closed ranges. The syntax for non-canonical range types is not fixed and can be discussed under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]
To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]
We note that striding by 0 should be always be a precondition failure.

Alternatives Considered

During the on-list discussion, we considered various scenarios that took closed/inclusive bounds into account or excluded open bounds for starting values. For example, we might have prohibited scenarios where multiple interpretations of an intended behavior might exist: is (0 ..< 9).striding(by: -2) a precondition failure? We settled on the simplest, most straight-forward implementation involving the fewest compiler warnings and the lowest likelihood of precondition failures. We subscribe to the "Dave Abrahams Philosophy": excessive special casing and warning scenarios more likely indicates bad language design than bad user comprehension.

Future Directions

We intend to follow up with an expanded operator vocabulary that includes fully open ranges (<.<), fully closed ranges (...) and both half open ranges (<.., ..<). These will support the full vocabulary laid out in the Detail Design section.

Upon adoption, the Swift community may consider expanding this approach to collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}
Striding offers a fundamental operation over collections. The consistent approach introduced in this proposal helps support the extension of stride semantics to collections.

Acknowlegements

Thanks, Dave Abrahams, Matthew Judge

_______________________________________________
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


(Xiaodi Wu) #15

Just as note, i think the sintax should be:

0...9
0..<9
0>..9
0>.<9

Because the intention is produce a number bigger than 0 (start). So greater
than zero less than nine.

That's not typically how it's written in math. When x is between two
values a and b, it's written a < x < b. The pointy end of the symbol
faces the smaller value. Here, 0 is the smaller value, so the pointy
end must face it, as in `0 <.. 9` and `0 <.< 9`.

···

On Sat, Apr 9, 2016 at 5:44 AM, Wallacy via swift-evolution <swift-evolution@swift.org> wrote:

Em sex, 8 de abr de 2016 22:50, Michel Fortin via swift-evolution > <swift-evolution@swift.org> escreveu:

Le 8 avr. 2016 à 14:37, Erica Sadun via swift-evolution >> <swift-evolution@swift.org> a écrit :
>
> (0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]

The above reads wrong to me. The expression has to be read differently
depending on the tinny detail that is the sign of the step that comes last:

* positive step: from 0 to 9 striding by 2
* negative step: to 0 from 9 striding by -2

Am I the only one thinking it's a bit too clever to swap the start and
stop parts like this?

--
Michel Fortin
https://michelf.ca

_______________________________________________
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


(Michel Fortin) #16

I know it's not possible to write 9...0. But I disagree that it's intuitive that a negative step value would reverse the start and stop values. I think to be consistent it should trap, like with a step of zero, because you can't go from 0 to 9 with a negative step.

It's especially important to realize that if the step value is a variable you will not be able to know which is the start value and which is the stop criterion by quickly inspecting the code because the sign of the step is hidden away in the value of that variable.

In other words, whether the loop begins at the start or the end of the range becomes a runtime decision. There is a branch in the generator that swaps the start and end values depending on that sign. If the step value is not a literal constant, inlining might not be able to elide that branch. Add another branch to make it trap when given a step of zero. And if it traps when the range is invalid (such as in 9...0) then that's another branch to check this when you form the range. That's 3 branches needed before even starting the loop.

Perhaps I'm just complaining about loosing the good performance characteristics of a C-style for loop. In the debate about this it was stated many times that `stride` could be used and would have the same performance. Is that promise about to be broken now?

On a final note: it's common, for me at least, to enter a loop with the start position already higher than the stop criterion, essentially doing something like `stride(from: 10, to 5, by 1)` which results in the loop being skipped. Translated into this new syntax, it'd become something like `(10...5).striding(by: 1)` which of course will trap because of the invalid range, thus necessitating an extra `if` to check for this. That clutters the code and adds even more branches. Perhaps the solution to this is to not trap on ranges with a negative length, although that's probably going to cause other headaches elsewhere.

···

Le 9 avr. 2016 à 4:25, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :

Note that it's not possible to write 9...0. Once that is clear, it's
fairly intuitive (IMO) to look to the stride size for indicating the
direction in which we stride, since that is the only other value there
is.

--
Michel Fortin
https://michelf.ca


(David Sweeris) #17

I can’t imagine any scenario in which getting the step's sign wrong wouldn’t just be a typo. Why not just assign it the correct sign during the init function?
(0 ... 6).striding(by: 2) // [0, 2, 4, 6], end > start, so stride = by
(6 ... 0).striding(by: 2) // [6, 4, 2, 0], start > end, so stride = -by

(OTOH, I don’t understand why 6…0 is currently a crash, rather than the, IMHO, obviously intended sequence of “6,5,4,3,2,1,0”, so maybe I’m not the best person to ask)

- Dave Sweeris

···

On Apr 9, 2016, at 5:58 PM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 9 Apr 2016, at 10:50, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

But, ranges are useful for more than just stride, and so I would say it doesn't make sense to have an intrinsic direction for Range, which pointedly has lowerBound and upperBound but not start and end. For sample, there shouldn't be (0...9).contains(1) and (9...0).contains(1).

While I can appreciate that, I wonder if it’s really that important that 0…9 and 9…0 are identical? As long as we can be clear on which direction of range we want to generate (so probably still need two new operators), can tell what the direction is, and can convert between them if we need to for some reason (reverse should be fine for that?), then I think we’re okay.

i.e- 9 … 0 would still cause an error at compile or run-time, we’d have some other operator for doing that, not sure what, plus 9 ..> 0, with both explicitly creating ranges in the reverse direction to avoid mistakes with the order or for computed indices. When it comes down to it the current Range has an implicitly forward direction, so I don’t see the problem with having the same in reverse personally.

I dunno, at the very least we might want to consider overloading the striding method as .striding(forwardBy:) and .striding(backwardBy:) or something similar, each taking a positive value of some kind to help avoid mistakes in cases where the stride size is computed rather than constant, this would make it more explicit at least.


(Dave Abrahams) #18

    While I’m in favour of the basic idea I think the operator selection is too
    complex, and I’m not sure about the need for negative strides. Really all I
    want are the following:

    (0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
    (0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
    (6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
    (6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

    Everything else should be coverable either by flipping the order, or using .
    reverse(). The main advantage is that there’s only one new operator to
    clarify the 6 ..> 0 case, though you could always just reuse the existing
    operator if you just interpret it as “x from 6 to, but not including, 0"

`.reverse()` returns an array, though, not a StrideTo<>,

.reversed() returns a ReversedCollection when the underlying collection
is bidirectional:

https://github.com/apple/swift/blob/swift-3-indexing-model/stdlib/public/core/Reverse.swift#L250

That's lazy and cheap.

···

on Sat Apr 09 2016, davesweeris-AT-mac.com wrote:

    On Apr 9, 2016, at 4:33 AM, Haravikk via swift-evolution > <swift-evolution@swift.org> wrote:

which means it’ll get in an infinite loop on infinite sequences.
This works fine: for i in stride(from: 0.0, to: Double.infinity, by:
M_PI) { if someTestInvolving(i) { break } ... }

But this never even starts executing the loop because of the infinite loop
inside `.reverse()`:
for i in stride(from: -Double.infinity, to: 0.0, by: M_PI).reverse() {
if someTestInvolving(i) { break }
...
}

- Dave Sweeris

--
Dave


(Xiaodi Wu) #19

IMO, 9...0 is a non-starter. Especially with Range taking on the role of
Interval, it's critical that they have no direction. Suppose you have two
ranges, 0...9 and 9...0. Is 0...9 == 9...0? IMO, two countable ranges r0
and r1 are equal if, for value in r0, r1.contains(value) == true, and vice
versa. (Sorry for the pseudocode; I'm typing on a phone.) However, if Range
has intrinsic direction, either equality ignores direction (ludicrous) or
0...9 != 9...0 (unacceptable).

Since we have `reverse()`, all ambiguity could be resolved by prohibiting
negative stride sizes altogether, I suppose. So, (0...9).striding(by:
2).reverse() could be the one Swifty way to stride backwards. Is that
objectionable to people?

···

On Sat, Apr 9, 2016 at 11:58 PM Haravikk <swift-evolution@haravikk.me> wrote:

On 9 Apr 2016, at 10:50, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

But, ranges are useful for more than just stride, and so I would say it
doesn't make sense to have an intrinsic direction for Range, which
pointedly has lowerBound and upperBound but not start and end. For sample,
there shouldn't be (0...9).contains(1) and (9...0).contains(1).

While I can appreciate that, I wonder if it’s really that important that
0…9 and 9…0 are identical? As long as we can be clear on which direction of
range we want to generate (so probably still need two new operators), can
tell what the direction is, and can convert between them if we need to for
some reason (reverse should be fine for that?), then I think we’re okay.

i.e- 9 … 0 would still cause an error at compile or run-time, we’d have
some other operator for doing that, not sure what, plus 9 ..> 0, with both
explicitly creating ranges in the reverse direction to avoid mistakes with
the order or for computed indices. When it comes down to it the current
Range has an implicitly forward direction, so I don’t see the problem with
having the same in reverse personally.

I dunno, at the very least we might want to consider overloading the
striding method as .striding(forwardBy:) and .striding(backwardBy:) or
something similar, each taking a positive value of some kind to help avoid
mistakes in cases where the stride size is computed rather than constant,
this would make it more explicit at least.

On Sat, Apr 9, 2016 at 10:33 AM Haravikk <swift-evolution@haravikk.me> > wrote:

While I’m in favour of the basic idea I think the operator selection is
too complex, and I’m not sure about the need for negative strides. Really
all I want are the following:

(0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
(0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
(6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
(6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

Everything else should be coverable either by flipping the order, or
using .reverse(). The main advantage is that there’s only one new operator
to clarify the 6 ..> 0 case, though you could always just reuse the
existing operator if you just interpret it as “x from 6 to, but not
including, 0"

I dunno, I just don’t think that introducing tons of new operators is
going to simplify things, and could lead to way more mistakes in practice;
the only mistake above would be putting the indices in the wrong order and
accidentally reversing the result.

Also, I’m against negative strides; while they could be useful for
convenience (avoid the need for .reverse or flipping values) I’m just not
sure that it makes sense. To me a stride is a distance, thus absolute, what
matters is the direction of the range. Naturally we’d need ranges with a
direction, but with collections requiring it to be in a particular order
(or ignoring it and flipping where necessary).

On 8 Apr 2016, at 19:37, Erica Sadun via swift-evolution < >> swift-evolution@swift.org> wrote:

Draft here:
https://gist.github.com/erica/a51a981ee0352235204692affa959307 Feedback
solicited, both positive and negative.
We've also got a related proposal about expanding ranges, which you can
look at here (
https://gist.github.com/erica/af92c541a0fb69fce1b7aaf8374a5aa9)
but we want to float this one first.

Thanks, -- E

   - Proposal: SE-XXXX
   <https://gist.github.com/erica/a51a981ee0352235204692affa959307/edit>
   - Author(s): Xiaodi Wu <https://github.com/xwu>, Pyry Jahkola
   <http://github.com/pyrtsa>, Nate Cook <http://github.com/natecook1000>
   , Erica Sadun <http://github.com/erica>
   - Status: TBD
   - Review manager: TBD

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#introduction>
Introduction

We propose to introduce a striding(by:) method on the revised 3.0 Range
type.

This proposal was discussed on the Swift Evolution list in the Feature
proposal: Range operator with step
<http://search.gmane.org/search.php?group=gmane.comp.lang.swift.evolution&query=Feature+proposal%3A+Range+operator+with+step> thread.
(Direct link
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/12801/focus=13051> to
original thread)

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#motivation>
Motivation

Updating Range for Swift 3 offers a window of opportunity to
simultaneously improve strides.

   -

   Under current Swift 3 plans, n.stride(to:/through:, by:) will be
   replaced with a standalone stride(from:, to:/through:, by:) function.
   We propose to replace this change with a method on ranges. Using a method
   reduces overall API surface area compared to free functions.
   -

   In its current incarnation, the standalone stride function uses
   confusing semantics. The current to implementation returns values in
   *[start, end)* and will never reach or get *to* end. The current
   through implementation returns values in *[start, end]*. It may never
   reach end and certainly never goes *through* that value. Our proposed
   method introduces simple, expected semantics that can be extended to both
   countable and continuous ranges, and to open and closed intervals (both
   half-open and fully-open).

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#detail-design>Detail
Design

The striding(by:) method is called on ranges. When used with a positive
step size, the count starts from the lower bound. With a negative step
size, the count starts from the upper bound. These bounds apply regardless
of whether they are inclusive or exclusive.

The following examples should cover all corner cases and include possible
cases should Swift 3 introduce a full complement of open and closed ranges.
The syntax for non-canonical range types is not fixed and can be discussed
under separate cover.

(0 ... 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 ..< 9).striding(by: 2) == [0, 2, 4, 6, 8]
(0 <.. 9).striding(by: 2) == [2, 4, 6, 8]
(0 <.< 9).striding(by: 2) == [2, 4, 6, 8]

(0 ... 9).striding(by: 3) == [0, 3, 6, 9]
(0 ..< 9).striding(by: 3) == [0, 3, 6]
(0 <.. 9).striding(by: 3) == [3, 6, 9]
(0 <.< 9).striding(by: 3) == [3, 6]

(0 ... 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 ..< 9).striding(by: -2) == [7, 5, 3, 1]
(0 <.. 9).striding(by: -2) == [9, 7, 5, 3, 1]
(0 <.< 9).striding(by: -2) == [7, 5, 3, 1]

(0 ... 9).striding(by: -3) == [9, 6, 3, 0]
(0 ..< 9).striding(by: -3) == [6, 3, 0]
(0 <.. 9).striding(by: -3) == [9, 6, 3]
(0 <.< 9).striding(by: -3) == [6, 3]

To reverse a stride, call reverse() on the results:

(0 ... 9).striding(by: 2).reverse() == [8, 6, 4, 2, 0]

We note that striding by 0 should be always be a precondition failure.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#alternatives-considered>Alternatives
Considered

During the on-list discussion, we considered various scenarios that took
closed/inclusive bounds into account or excluded open bounds for starting
values. For example, we might have prohibited scenarios where multiple
interpretations of an intended behavior might exist: is (0 ..<
9).striding(by: -2) a precondition failure? We settled on the simplest,
most straight-forward implementation involving the fewest compiler warnings
and the lowest likelihood of precondition failures. We subscribe to the
"Dave Abrahams Philosophy": excessive special casing and warning scenarios
more likely indicates bad language design than bad user comprehension.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#future-directions>Future
Directions

We intend to follow up with an expanded operator vocabulary that includes
fully open ranges (<.<), fully closed ranges (...) and both half open
ranges (<.., ..<). These will support the full vocabulary laid out in
the Detail Design section.

Upon adoption, the Swift community may consider expanding this approach
to collection indices, for example:

let a = [8, 6, 7, 5, 3, 0, 9]
for e in a.striding(by: 3) {
    print(e) // 8, then 5, then 9
}

Striding offers a fundamental operation over collections. The consistent
approach introduced in this proposal
<http://article.gmane.org/gmane.comp.lang.swift.evolution/13936> helps
support the extension of stride semantics to collections.

<https://gist.github.com/erica/a51a981ee0352235204692affa959307#acknowlegements>
Acknowlegements
Thanks, Dave Abrahams, Matthew Judge

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


(Xiaodi Wu) #20

Yikes. Not too concerned about the infinite loop issue, as floating point
strides when fixed to avoid error accumulation will necessarily enforce a
finite number of steps. However, you're talking a regular, not-at-all-lazy
Array being returned? That would be not good at all...

···

On Sun, Apr 10, 2016 at 12:29 AM Dave via swift-evolution < swift-evolution@swift.org> wrote:

On Apr 9, 2016, at 4:33 AM, Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:

While I’m in favour of the basic idea I think the operator selection is
too complex, and I’m not sure about the need for negative strides. Really
all I want are the following:

(0 ... 6).striding(by: 2) // [0, 2, 4, 6] x from 0 to 6
(0 ..< 6).striding(by: 2) // [0, 2, 4] x from 0 while <6
(6 ... 0).striding(by: 2) // [6, 4, 2, 0] x from 6 to 0
(6 ..> 0).striding(by: 2) // [6, 4, 2] x from 6 while >0

Everything else should be coverable either by flipping the order, or using
.reverse(). The main advantage is that there’s only one new operator to
clarify the 6 ..> 0 case, though you could always just reuse the existing
operator if you just interpret it as “x from 6 to, but not including, 0"

`.reverse()` returns an array, though, not a StrideTo<>, which means it’ll
get in an infinite loop on infinite sequences. This works fine:
for i in stride(from: 0.0, to: Double.infinity, by: M_PI) {
    if someTestInvolving(i) { break }
    ...
}

But this never even starts executing the loop because of the infinite loop
inside `.reverse()`:
for i in stride(from: -Double.infinity, to: 0.0, by: M_PI).reverse() {
    if someTestInvolving(i) { break }
    ...
}

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