Milos, you make good points. This thread is really long and hard to
follow, so I'll reply inline below with some observations that have
been made in the past, which I think address some of them. See if you
like where things are headed.

On Sat, Apr 2, 2016 at 8:05 PM, Milos Rankovic via swift-evolution
<swift-evolution@swift.org> wrote:
> `Strideable` types represent an often needed generalisation of `Range` and
> `IntervalType`s. However, `Strideable`’s two `stride` methods are far too
> verbose and unbalanced (in contrast to the natural look and feel of the two
> interval operators). Examples like the following raise a number of issues:
>
>     1.stride(through: 5, by: 2)  // 1, 3, 5
>
>     1.stride(through: 5, by: -2) // []
>
> 1. The method's verbosity keeps the bounds too far apart.
>
> 2. The dot syntax suggests that something is being done to the start bound,
> with the end bound playing the role of an argument, all of which does not
> really reflect the semantics of the call.

An older syntax is being restored in Swift 3: `stride(from: 1, to: 5,
by: 2)` and `stride(from: 1, through: 5, by: 2)`, and dot syntax is
being removed. Bounds are now next to each other, and the start and
end values are now visually equals.

> 3. The direction in which we advance from one end to another of the interval
> is provided twice: once by the order of the bounds and then again by the
> sign of the stride argument.

The stride direction is strictly given by the sign of the last
argument; `stride(from: 1, to: -5, by: 2)` is an empty sequence,
because you cannot get from start to end by -2. See next comment for
why I think this is a feature, not a bug.

> 4. Given the conceptual proximity of `Strideable`, `IntervalType` and
> `Range`, one would expect analogous ways of constructing them.
>
> 5. The word “stride” is not particularly friendly to programmers whose first
> language is not English (again in contrast to the interval operators). This
> is compounded by the distinction between `to` and `through` parameters.
>
> As already noted in this thread, we could simply extend the existing types:
>
>     extension ClosedInterval where Bound : Strideable {
>         func by(stride: Bound.Stride) -> StrideThrough<Bound> {
>             let (s, e) = stride < 0 ? (end, start) : (start, end)
>             return s.stride(through: e, by: stride)
>         }
>     }
>
>     extension HalfOpenInterval where Bound : Strideable {
>         func by(stride: Bound.Stride) -> StrideTo<Bound> {
>             let (s, e) = stride < 0 ? (end, start) : (start, end)
>             return s.stride(to: e, by: stride)
>         }
>     }
>
> So that:
>
>     (1...5).by(2)  // 1, 3, 5
>     (1..<5).by(2)  // 1, 3
>
>     (1...5).by(-2) // 5, 3, 1
>     (1..<5).by(-2) // 5, 3

Yes, I do think that's a great idea, as do other people! Because Dave
A is making some big changes to Range (and Intervals are going away,
leaving only Range), I haven't tried to extend Range in my last
proof-of-concept, but I think there's momentum to add a
`striding(by:)` method to Range to do exactly that, `striding(by:)`
being more clear than `by(_:)`.

One difference between `Range.striding(by:)` and `stride(from:to:by:)`
will be that it's a fatal error to try to construct `1..<(-5)` as a
Range, but if you read the comments in the code for StrideTo, the
original designers of stride explicitly wanted `stride(from: 1, to:
-5, by: 1)` to be allowed. When you can't get from start to end by the
chosen stride, the result is an empty sequence instead of a fatal
error. There may be use cases where that behavior is preferred, so I'm
in favor of adding `striding(by:)` to Range but also keeping
`stride(...)`.

> More exotically, we could make use of subscripts:
>
>     extension ClosedInterval where Bound : Strideable {
>         subscript(stride: Bound.Stride) -> StrideThrough<Bound> {
>             return by(stride)
>         }
>     }
>
>     extension HalfOpenInterval where Bound : Strideable {
>         subscript(stride: Bound.Stride) -> StrideTo<Bound> {
>             return by(stride)
>         }
>     }
>
>     (1...5)[-2] // 5, 3, 1
>
> Or introduce a new, or overload an existing operator, with precedence just
> lower than the two interval operators. For example:
>
>     func > <T> (i: ClosedInterval<T>, stride: T.Stride) -> StrideThrough<T>
> {
>         return i.start.stride(through: i.end, by: stride)
>     }
>
>     func < <T> (i: ClosedInterval<T>, stride: T.Stride) -> StrideThrough<T>
> {
>         return i.end.stride(through: i.start, by: -stride)
>     }
>
>     func > <T> (i: HalfOpenInterval<T>, stride: T.Stride) -> StrideTo<T> {
>         return i.start.stride(to: i.end, by: stride)
>     }
>
>     func < <T> (i: HalfOpenInterval<T>, stride: T.Stride) -> StrideTo<T> {
>         return i.end.stride(to: i.start, by: -stride)
>     }
>
>     for i in 1...5 < 2 {
>         i // 5, 3, 1
>     }
>
>     for i in 1...5 > 2 {
>         i // 1, 3, 5
>     }

I've suggested something like that to be possible earlier in the
thread; didn't get too much of a positive reception. People seem to
like `by(_:)` or `striding(by:)` though.

>
> Not to mention a C-style `for` loop lookalike:
>
>     for i in (1 to 5 by 2) {
>         i // 1, 3, 5
>     }
>
> Obviously, this whole thread is related to the C-style `for` loop (which is
> more general than all of the above solutions) as well as to Haskell-style
> list comprehension syntax (which remains enviable). Nevertheless, I do think
> that a focused, lightweight feature would be the best fit for such a common
> need (just think, for example, how often are such sequences used for
> instructional purposes).
>
> One other possibility is to introduce open-ended, infinite sequences defined
> by a single bound and a stride:
>
>     // infinite sequence, starting with 5 and advancing by -2
>     (5..|-2)
>
> … which could be optionally closed by one of the interval operators:
>
>     (5..|-2)...1
>
> I’ve read somewhere that the “interval is going away”, in which case, a new
> tertiary operator may be worth considering since striding is such a
> fundamental operation. Or really any of the above – just not sticking to the
> existing `stride` methods!
>
> milos
>
> _______________________________________________
> 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

Reply via email to