[Discussion] stride behavior and a little bit of a call-back to digital numbers

Erica Sadun via swift-evolution

Erica Sadun via swift-evolution
<swift-evolution@swift.org

If you were addressing issue 2, what approach would you suggest:

* Differentiating integer strides from floating point, and minimizing
floating point errors?
* Leaving the tech as-is with minimal code
change with a relatively high benefit?

Arguably, floating point types shouldn't conform to Strideable at all, on
the general principle that genericizing arithmetic over ints and floats is
a trap. That would free floating point types to provide their own
specialized implementation of the stride methods.

As if I don't already get enough grief from the C-for-loop people, now this? They'll be
burning semicolons on my front lawn at this rate. (And let me mention again that this
problem affects C-for-loop just as much as it does strides.)

Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

···

<swift-evolution@swift.org> wrote:

On Feb 27, 2016, at 6:28 PM, Joe Groff via swift-evolution >> <swift-evolution@swift.org> wrote:
<mailto:swift-evolution@swift.org>> wrote:

In both cases, I'd still prefer the semantics to *go through* the end
point, not just stop at it, which is issue 1.

I'll have to defer to domain experts on this one. It seems superficially
appealing at least. If your only motivation is to get 1.0 through 2.0 by
0.1 to include 2.0, though, that feels like weak justification to me, since
that's a symptom of a different problem.

This sounds like it reduces to the following:

1. Leave as is, broken, requiring workarounds for nearly all floating point cases

2. Leave as is but remove floating point stride support, tick off
forloopians. Introduce floating point stride-alike,
which isn't terribly difficult but will need a different something since
`Stride` is built on `SignedNumberType`.

3. (This) Introduce a fix to make floating point less sucky and integers
more semantically fitting, with
minimal code change. Issue FIXME warnings during migration to warn
against changed semantics.

4. Do something else. Magic happens. Profit.

-- E

How about something like fstride.swift · GitHub

-- E

···

On Feb 27, 2016, at 7:30 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:
Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

I’d like to add a couple of thoughts to the discussion.

I agree that the current Strideable naming is surprising, and toward/to instead of to/through would have been more appropriate.

I’m not so sure that having a proper ‘through’ method that would pass through the endpoint would be useful, and that it would do anything to solve the floating point issues.

The specific example that started the discussion was 1.0 through 2.0 by 0.1. The expected result was to end exactly at 2.0, instead it ended at 1.9 because `by`+epsilon was added at every step. Changing the behavior of `through`to end past the endpoint would have solved this case.

However, I suspect that changing the stride values you could end in the opposite condition, where `by`-epsilon is added at every step, and so with a changed `through` behavior you would end at the equivalent of 2.1; which is equally wrong.

The root of the problem is that Strideable is clearly designed with exact (not necessarily integer, but exact) math in mind. Floating point values shouldn’t conform to it, as they need different semantics.

In my experience, when dealing with floating point values in loops it has always been one of two cases:

1) (most common) iterate from A to *exactly* B, doing steps *as close as possible* to S
2) (less common) iterate from A to *as close as possible* to B, doing steps of *exactly* S

1) would be implemented with integer math, computing the number of steps needed and then interpolating between A and B. This guarantees that the final iteration is exactly on B, however the difference between two steps would not be exactly S.

2) would be implemented by tweaking the end condition so that the final iteration can be either before or after B—whichever is closest.

···


Nicola

I've updated the proposal to take this into account:stridethrough.md · GitHub

It makes two core recommendations:

1. Adjust stride function semantics to expand from two to three functions, renaming them appropriately.
2. Break floating point strides away from Strideable to provide their own stride method implementations.

-- E

> On Feb 27, 2016, at 8:07 PM, Charles Kissinger<crk@akkyra.com(mailto:crk@akkyra.com)>wrote:
>
> > On Feb 27, 2016, at 5:38 PM, Erica Sadun<erica@ericasadun.com(mailto:erica@ericasadun.com)>wrote:
> > Would you accept a third version then? towards (to, `[a, b)`), to (through, `[a, b]`), and through (new, `[a,>=b]`<-- not sure that even has a representation)?
>
> No objection here. It’s a really interesting problem you’ve pointed out. I’m not sure yet the best way to minimize it.
>
> —CK
> > -- E
> >
> >
> > > On Feb 27, 2016, at 6:25 PM, Charles Kissinger<crk@akkyra.com(mailto:crk@akkyra.com)>wrote:
> > > Hi Erica,
> > >
> > > With your suggested change, there would be no way to specify a precise upper bound for a sequence (except for integers in specific cases). The current pair of functions provide numbers in the intervals [a, b) and [a,b], which is what is needed to cover all use cases. With your change, the two functions would produce sequences that potentially lie either in the interval [a, b) for "stride to” or [a, infinity] for "stride through” since the size of the stride isn’t necessarily known at compile time or predictable.
> > >
> > > In addition to breaking existing code, it would not cover every use case. Sometimes you won’t know the stride until runtime, but you know it has to be able to reach but not exceed a certain value.
> > >
> > > —CK
> > >
> > > > On Feb 27, 2016, at 3:27 PM, Erica Sadun via swift-evolution<swift-evolution@swift.org(mailto:swift-evolution@swift.org)>wrote:
> > > > Following up to myself. Thoughts and feedback are welcome. -- Erica
> > > >
> > > > Changing the Behavior of StrideThroughGenerator
> > > >
> > > > Swift offers two stride functions,stride(to:, by:)andstride(through:, by:). I propose to change the way thethroughvariation works.
> > > >
> > > > Current Art
> > > >
> > > > AStrideable tosequence returns the sequence of values (self,self + stride,self + stride + stride, …last) wherelastis the last value in
> > > > the progression that is less thanend.
> > > >
> > > >
> > > > AStrideable throughsequence currently returns the sequence of values (self,self + stride,self + tride + stride, …last) wherelastis the last value in the progression less than or equal toend. There is no guarantee thatendis an element of the sequence.
> > > >
> > > >
> > > > Under the current implementation, each floating point addition accrues errors. The progression never reaches 2.0.
> > > >
> > > > print(Array(1.0.stride(through:2.0,by:0.1)))/// Prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
> > > >
> > > > To force the progression to include 2.0, you must add an (ugly) epsilon, as in the following example:
> > > >
> > > > print(Array(1.0.stride(through:2.01,by:0.1)))/// Prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
> > > >
> > > > This is problematic for the following reasons:
> > > >
> > > > The name of the calling function “through” suggests the progression will passthroughthe end point before stopping
> > > > Floating point calls present an extremely common use-case
> > > > It’s unreasonable to expect developers to consider every case of “will floating point math prevent my progression from actually reaching the end point, which has already been differentiated by usingthroughrather thanto”
> > > >
> > > > Proposed Modifications
> > > >
> > > > I recommend the following changes:
> > > >
> > > > Change the documentation text from
> > > > >
> > > > > AStrideable throughsequence currently returns the sequence of values (self,self + stride,self + stride + stride, …last) wherelastis the last value in the progressionless than or equal toend. There is no guarantee thatendis an element of the sequence.
> > > > >
> > > >
> > > >
> > > > to
> > > >
> > > > >
> > > > > AStrideable throughsequence currently returns the sequence of values (self,self + stride,self + stride + stride, …last) wherelastis the last value in the progressiongreater than or equal toend. There is no guarantee thatendis an element of the sequence.
> > > > >
> > > >
> > > > Modify the implementation
> > > >
> > > > Current:
> > > >
> > > > ///Advanceto thenextelementandreturnit,or`nil`ifnonext///element exists. public mutating funcnext() ->Element? {ifdone {returnnil}ifstride>0? current>=end:current<=end{ifcurrent ==end{ done =truereturncurrent }returnnil} let result = current current += stridereturnresult }
> > > >
> > > > Proposed:
> > > >
> > > > ///Advanceto thenextelementandreturnit,or`nil`ifnonext///element exists. public mutating funcnext() ->Element? {ifdone {returnnil}ifstride>0? current>=end:current<=end{//NOTE:`current>=end`andnot`current ==end`ifcurrent>=end{ done =truereturncurrent }returnnil} let result = current current += stridereturnresult } }
> > > > Introduced Changes
> > > >
> > > > Under these changes, the following progression ends at 2.0 not 1.9:
> > > >
> > > > print(Array(1.0.stride(through:2.0,by:0.1)))// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
> > > >
> > > > Integer progressions are unchanged:
> > > >
> > > > print(Array(1.stride(through2:10,by:1)))/// prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
> > > >
> > > > Floating point strides will extend up-to or past thethroughvalue:
> > > >
> > > > // Oldprint(Array(1.0.stride(through:1.9,by:0.25)))// prints [1.0, 1.25, 1.5, 1.75]// Newprint(Array(1.0.stride(through:1.9,by:0.25)))// prints [1.0, 1.25, 1.5, 1.75, 2.0]
> > > > Alternates Considered
> > > >
> > > > Other changes could include:
> > > >
> > > > Introducing adigitalStridefunction with a set precision that works in integer math, multiplying each value by 10n, converting to integers, and then working back to floating point after each change
> > > > Counting expected iterations by forming(max - min) / by, e.g.(2.0 - 1.0) / 0.1, which is 10, and performing each step as a pro-rated progression along those steps, which would remove most of the accumulated floating point errors along the way.
> > > > Introducing aDecimalNumbertype, with its ownstridemethods, e.g.DecimalNumber(1.0).stride(through:DecimalNumber(2.0), by: DecimalNumber(0.1)).
> > > >
> > > >
> > > >
> > > > > On Feb 26, 2016, at 5:12 PM, Erica Sadun via swift-evolution<swift-evolution@swift.org(mailto:swift-evolution@swift.org)>wrote:
> > > > > I have a problem with the way floating point ranges work with striding:
> > > > > > 1.0.stride(through: 2.0, by: 0.1)returns the sequence[1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9].
> > > > > > Documentation: "It returns the sequence where last is less than or equal to `end`."
> > > > >
> > > > > (And yes, the same issue exists with tradition C-style for loops).
> > > > >
> > > > > Would it be really horrible if the implementation and definition was changed to:
> > > > > > "It returns the sequence where last is greater than or equal to `end`?"
> > > > > >
> > > > >
> > > > > This would offer no change for integers, and include 2.0 for floating point sequences.
> > > > >
> > > > > Alternatively, could there bedecimalStride? Using Double but a rounding system with a fixed number of decimal places (e.g. 1, 2, 3), to ensure at least the end point is hit? It might look like:
> > > > > > 1.0.stride(through: 2.0, by: 0.1, places: 1)
> > > > >
> > > > >
> > > > >
> > > > > I know there have been several discussions on-list about decimal number systems (Re: Is there a need for a Decimal type?(http://article.gmane.org/gmane.comp.lang.swift.evolution/7130/match=decimal\)) as well, but this could fix an ongoing annoyance without a major change.
> > > > >
> > > > > Thanks for your thoughts,
> > > > >
> > > > > -- Erica
> > > > >
> > > > > _______________________________________________
> > > > > 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(mailto:swift-evolution@swift.org)
> > > > https://lists.swift.org/mailman/listinfo/swift-evolution
> > >
> >
>

Cheers,

Curt

···

On Feb 27, 2016, at 10:03 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

I've updated the proposal to take this into account: stridethrough.md · GitHub

It makes two core recommendations:

1. Adjust stride function semantics to expand from two to three functions, renaming them appropriately.
2. Break floating point strides away from Strideable to provide their own stride method implementations.

I don't have a strong opinion on the proposal. These sorts of strides are rare in the problem domains I work in. I would like to nitpick the proposed documentation for the new stride(through:,by:). The revised proposal gives the new documentation as:

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression greater than or equal to end. There is no guarantee that end is an element of the sequence.

That should read “where last is the **first** value in the progression that is greater than or equal to end.“ As proposed, last would be the largest number representable in self’s type that is a multiple of stride greater than self. That’s not the intent.

Cheers,

Curt

-------------------------
Curt Clifton, PhD
Software Developer
The Omni Group
www.curtclifton.net

This implementation with:
        let current = start + Double(self.iteration) * stride

does have the advantage of also fixing the following infinite loop (even though this example is bit silly)

for data in 1_000_000_000_000_000.0.stride(to:1_000_000_000_000_001.0, by:0.05)
{
    print(data)
}

Dany

···

Le 27 févr. 2016 à 21:54, Erica Sadun via swift-evolution <swift-evolution@swift.org> a écrit :

On Feb 27, 2016, at 7:30 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

How about something like fstride.swift · GitHub

I've updated the proposal to take this into account: stridethrough.md · GitHub

It makes two core recommendations:

1. Adjust stride function semantics to expand from two to three functions, renaming them appropriately.
2. Break floating point strides away from Strideable to provide their own stride method implementations.

I'm just a humble superannuated practitioner, but I've lost count of the
many times that I've alerted people to the problems of binary floating
point.

I would argue that one of the issues to be fixed in
  print(Array(1.0.stride(through: 2.0, by: 0.1)))
is that the compiler should, at least, emit a warning when seeing 0.1
(or 0.01 etc.) along the lines of "not a valid floating point constant".

No doubt the appropriate xyzLiteralConvertible type could be made to
complain strenuously.

The safety aims of Swift are very commendable but I submit tolerating a
0.1 constant to be assigned to a binary floating point type is extremely
unsafe!

Of course requiring floating point constants to be always written in
binary/hex is too much to ask of the "forloopians" (LOVE this word!),
but isn't having a warning reasonable?

Perhaps, even, we could have a notation like ~0.1 to turn the warning
off and to tell the compiler to use the "binary value most close to"
that constant.

I probably lack the qualifications to write this up according to the
required formalities but will be glad to assist anyone to do so, if
necessary.

···

On 2/28/16 03:03, Erica Sadun via swift-evolution wrote:

On Feb 27, 2016, at 8:07 PM, Charles Kissinger <crk@akkyra.com> wrote:

On Feb 27, 2016, at 5:38 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

Would you accept a third version then? towards (to, `[a, b)`), to (through, `[a, b]`), and through (new, `[a, >=b]` <-- not sure that even has a representation)?

No objection here. It’s a really interesting problem you’ve pointed out. I’m not sure yet the best way to minimize it.

—CK

-- E

On Feb 27, 2016, at 6:25 PM, Charles Kissinger <crk@akkyra.com <mailto:crk@akkyra.com>> wrote:

Hi Erica,

With your suggested change, there would be no way to specify a precise upper bound for a sequence (except for integers in specific cases). The current pair of functions provide numbers in the intervals [a, b) and [a,b], which is what is needed to cover all use cases. With your change, the two functions would produce sequences that potentially lie either in the interval [a, b) for "stride to” or [a, infinity] for "stride through” since the size of the stride isn’t necessarily known at compile time or predictable.

In addition to breaking existing code, it would not cover every use case. Sometimes you won’t know the stride until runtime, but you know it has to be able to reach but not exceed a certain value.

—CK

On Feb 27, 2016, at 3:27 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Following up to myself. Thoughts and feedback are welcome. -- Erica

Changing the Behavior of StrideThroughGenerator

Swift offers two stride functions, stride(to:, by:) and stride(through:, by:). I propose to change the way the through variation works.

Current Art

A Strideable to sequence returns the sequence of values (self, self + stride, self + stride + stride, … last) where last is the last value in
the progression that is less than end.

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + tride + stride, … last) where last is the last value in the progression less than or equal to end. There is no guarantee that end is an element of the sequence.

Under the current implementation, each floating point addition accrues errors. The progression never reaches 2.0.

print(Array(1.0.stride(through: 2.0, by: 0.1)))
/// Prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
To force the progression to include 2.0, you must add an (ugly) epsilon, as in the following example:

print(Array(1.0.stride(through: 2.01, by: 0.1)))
/// Prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
This is problematic for the following reasons:

The name of the calling function “through” suggests the progression will pass through the end point before stopping
Floating point calls present an extremely common use-case
It’s unreasonable to expect developers to consider every case of “will floating point math prevent my progression from actually reaching the end point, which has already been differentiated by using through rather than to”
Proposed Modifications

I recommend the following changes:

Change the documentation text from

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + stride + stride, … last) where last is the last value in the progression less than or equal to end. There is no guarantee that end is an element of the sequence.

to

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + stride + stride, … last) where last is the last value in the progression greater than or equal to end. There is no guarantee that end is an element of the sequence.

Modify the implementation

Current:

    /// Advance to the next element and return it, or `nil` if no next
    /// element exists.
    public mutating func next() -> Element? {
        if done {
            return nil
        }
        if stride > 0 ? current >= end : current <= end {
            if current == end {
                done = true
                return current
            }
            return nil
        }
        let result = current
        current += stride
        return result
    }
Proposed:

    /// Advance to the next element and return it, or `nil` if no next
    /// element exists.
    public mutating func next() -> Element? {
        if done {
            return nil
        }
        if stride > 0 ? current >= end : current <= end {
            // NOTE: `current >= end` and not `current == end`
            if current >= end {
                done = true
                return current
            }
            return nil
        }
        let result = current
        current += stride
        return result
    }
}
Introduced Changes

Under these changes, the following progression ends at 2.0 not 1.9:

print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
Integer progressions are unchanged:

print(Array(1.stride(through2: 10, by: 1)))
/// prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Floating point strides will extend up-to or past the through value:

// Old
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75]

// New
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75, 2.0]
Alternates Considered

Other changes could include:

Introducing a digitalStride function with a set precision that works in integer math, multiplying each value by 10n, converting to integers, and then working back to floating point after each change
Counting expected iterations by forming (max - min) / by, e.g. (2.0 - 1.0) / 0.1, which is 10, and performing each step as a pro-rated progression along those steps, which would remove most of the accumulated floating point errors along the way.
Introducing a DecimalNumber type, with its own stride methods, e.g. DecimalNumber(1.0).stride(through:DecimalNumber(2.0), by: DecimalNumber(0.1)).

On Feb 26, 2016, at 5:12 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I have a problem with the way floating point ranges work with striding:
1.0.stride(through: 2.0, by: 0.1) returns the sequence [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9].
Documentation: "It returns the sequence where last is less than or equal to `end`."
(And yes, the same issue exists with tradition C-style for loops).

Would it be really horrible if the implementation and definition was changed to:
"It returns the sequence where last is greater than or equal to `end`?"
This would offer no change for integers, and include 2.0 for floating point sequences.

Alternatively, could there be decimalStride? Using Double but a rounding system with a fixed number of decimal places (e.g. 1, 2, 3), to ensure at least the end point is hit? It might look like:
1.0.stride(through: 2.0, by: 0.1, places: 1)

I know there have been several discussions on-list about decimal number systems (Re: Is there a need for a Decimal type? <http://article.gmane.org/gmane.comp.lang.swift.evolution/7130/match=decimal&gt;\) as well, but this could fix an ongoing annoyance without a major change.

Thanks for your thoughts,

-- Erica

_______________________________________________
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 <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

--
Rainer Brockerhoff <rainer@brockerhoff.net>
Belo Horizonte, Brazil
"In the affairs of others even fools are wise
In their own business even sages err."

Functionally that seems reasonable. IMO it wouldn't be a problem to call the Double variant `stride(through:by:)` to match the integer form. Just for fun, you could simplify the condition here:

        if stride > 0.0 ? current >= end : current <= end {

by using 'if signbit(current - end) == signbit(stride)' instead.

-Joe

···

On Feb 27, 2016, at 6:54 PM, Erica Sadun <erica@ericasadun.com> wrote:

On Feb 27, 2016, at 7:30 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

How about something like fstride.swift · GitHub

Reload and check please. stridethrough.md · GitHub

-- E

···

On Feb 28, 2016, at 9:55 AM, Curt Clifton <curt@omnigroup.com> wrote:

Cheers,

Curt

On Feb 27, 2016, at 10:03 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I've updated the proposal to take this into account: stridethrough.md · GitHub

It makes two core recommendations:

1. Adjust stride function semantics to expand from two to three functions, renaming them appropriately.
2. Break floating point strides away from Strideable to provide their own stride method implementations.

I don't have a strong opinion on the proposal. These sorts of strides are rare in the problem domains I work in. I would like to nitpick the proposed documentation for the new stride(through:,by:). The revised proposal gives the new documentation as:

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression greater than or equal to end. There is no guarantee that end is an element of the sequence.

That should read “where last is the **first** value in the progression that is greater than or equal to end.“ As proposed, last would be the largest number representable in self’s type that is a multiple of stride greater than self. That’s not the intent.

Cheers,

Curt

-------------------------
Curt Clifton, PhD
Software Developer
The Omni Group
www.curtclifton.net <¯\_(ツ)_/¯—curtclifton.net;

Updated proposal, including code tweak: stridethrough.md · GitHub

I get your point, but I'm leaving this as `fstride` right now. The proposal is *already* breaking several rules of Lattner. It's proposing not one but two distinct changes, which should probably be two separate proposals: "Changing Strideable semantics" and "Decoupling Floating Point Strides from Generic Implementations". If decoupled, I'll recommend keeping stride name consistency. (I kind of recommend it here to, but I'll punch it more.)

I'm about theeeeeeeesclose to separating them into those two proposals, and would like your feedback on that.

-- Erica

···

On Feb 29, 2016, at 4:48 PM, Joe Groff <jgroff@apple.com> wrote:

On Feb 27, 2016, at 6:54 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Feb 27, 2016, at 7:30 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

How about something like fstride.swift · GitHub

Functionally that seems reasonable. IMO it wouldn't be a problem to call the Double variant `stride(through:by:)` to match the integer form. Just for fun, you could simplify the condition here:

        if stride > 0.0 ? current >= end : current <= end {

by using 'if signbit(current - end) == signbit(stride)' instead.

-Joe

Looks good!

Cheers,

Curt

···

On Feb 28, 2016, at 9:18 AM, Erica Sadun <erica@ericasadun.com> wrote:

Reload and check please. stridethrough.md · GitHub

-- E

On Feb 28, 2016, at 9:55 AM, Curt Clifton <curt@omnigroup.com> wrote:

Cheers,

Curt

On Feb 27, 2016, at 10:03 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

I've updated the proposal to take this into account: stridethrough.md · GitHub

It makes two core recommendations:

1. Adjust stride function semantics to expand from two to three functions, renaming them appropriately.
2. Break floating point strides away from Strideable to provide their own stride method implementations.

I don't have a strong opinion on the proposal. These sorts of strides are rare in the problem domains I work in. I would like to nitpick the proposed documentation for the new stride(through:,by:). The revised proposal gives the new documentation as:

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression greater than or equal to end. There is no guarantee that end is an element of the sequence.

That should read “where last is the **first** value in the progression that is greater than or equal to end.“ As proposed, last would be the largest number representable in self’s type that is a multiple of stride greater than self. That’s not the intent.

Cheers,

Curt

-------------------------
Curt Clifton, PhD
Software Developer
The Omni Group
www.curtclifton.net

I agree, splitting into two proposals is a good idea.

-Joe

···

On Feb 29, 2016, at 4:02 PM, Erica Sadun <erica@ericasadun.com> wrote:

On Feb 29, 2016, at 4:48 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Feb 27, 2016, at 6:54 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Feb 27, 2016, at 7:30 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

How about something like fstride.swift · GitHub

Functionally that seems reasonable. IMO it wouldn't be a problem to call the Double variant `stride(through:by:)` to match the integer form. Just for fun, you could simplify the condition here:

        if stride > 0.0 ? current >= end : current <= end {

by using 'if signbit(current - end) == signbit(stride)' instead.

-Joe

Updated proposal, including code tweak: stridethrough.md · GitHub

I get your point, but I'm leaving this as `fstride` right now. The proposal is *already* breaking several rules of Lattner. It's proposing not one but two distinct changes, which should probably be two separate proposals: "Changing Strideable semantics" and "Decoupling Floating Point Strides from Generic Implementations". If decoupled, I'll recommend keeping stride name consistency. (I kind of recommend it here to, but I'll punch it more.)

I'm about theeeeeeeesclose to separating them into those two proposals, and would like your feedback on that.

Let me go ahead and do that.

-- E

···

On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff@apple.com> wrote:

On Feb 29, 2016, at 4:02 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Feb 29, 2016, at 4:48 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Feb 27, 2016, at 6:54 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On Feb 27, 2016, at 7:30 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Forloopians probably don't care whether Float is Strideable, as long as it
has working stride methods. If those methods are better than what they'd
write naively, well, that's even better justification for taking the C loop
away.

How about something like fstride.swift · GitHub

Functionally that seems reasonable. IMO it wouldn't be a problem to call the Double variant `stride(through:by:)` to match the integer form. Just for fun, you could simplify the condition here:

        if stride > 0.0 ? current >= end : current <= end {

by using 'if signbit(current - end) == signbit(stride)' instead.

-Joe

Updated proposal, including code tweak: stridethrough.md · GitHub

I get your point, but I'm leaving this as `fstride` right now. The proposal is *already* breaking several rules of Lattner. It's proposing not one but two distinct changes, which should probably be two separate proposals: "Changing Strideable semantics" and "Decoupling Floating Point Strides from Generic Implementations". If decoupled, I'll recommend keeping stride name consistency. (I kind of recommend it here to, but I'll punch it more.)

I'm about theeeeeeeesclose to separating them into those two proposals, and would like your feedback on that.

I agree, splitting into two proposals is a good idea.

-Joe

Conventionalizing stride semantics

Proposal: SE-00NN
Author(s): Erica Sadun <http://github.com/erica&gt;
Status: TBD
Review manager: TBD
Swift offers two stride functions, stride(to:, by:) and stride(through:, by:). This proposal introduces a third style and renames the existing to and through styles.

This proposal was discussed on-list in the "[Discussion] stride behavior and a little bit of a call-back to digital numbers" <http://article.gmane.org/gmane.comp.lang.swift.evolution/8014&gt;thread\.

<stride.md · GitHub

Strideable's function names do not semantically match the progressions they generate. Values produced by throughdo not pass through an end point; they stop at or before that fence. For example, 1.stride(through: 10, by: 8) returns the progress (1, 9), not (1, 9, 17). Similarly, its to function values reaches its end point. 1.stride(to:4, by:1) returns 1, 2, and 3. It never makes it to 4:

The current Swift definition of to returns values in [start, end) and will never reach end. In other words, you will never get to end.
The current Swift definition of through returns values in [start, end]. It may never reach end and certainly never goes through that value.
Some definitions with the help of the New Oxford American Dictionary

Moving to a value expresses "a point reached at the end of a range".
To pass through a value, you should move beyond "the position or location of something beyond or at the far end of (an opening or an obstacle)".
To move towards a value is to get "close or closer" or "getting closer to achieving (a goal)".
<stride.md · GitHub Art

A Strideable to sequence returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression that is less than end.

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + tride + stride, ... last) where last is the last value in the progression less than or equal to end. There is no guarantee that end is an element of the sequence.

The name of the calling function through suggests the progression will pass through the end point before stopping. It does not. The name to suggests a progression will attempt to arrive at an end point. It does not.

<stride.md · GitHub Design

When striding to or through a number, the behavior does not match the meaning of the word. Swift should provide three stride styles not two.

Style 1: [start, end) by interval
This style is currently called to. I propose to rename it towards as each value works towards end. The final value in the progression is less than end

Style 2: [start, end] by interval
This style is currently called through. I propose to rename it to. The progression concludes with a value that is less than or equal to end. Swift provides no guarantee that end is an element of the sequence.

Style 3: [start, >=end] by interval
I propose to introduce a new style called through. The final value is guaranteed to pass through end, either by finishing on end or past end. The final value is strictly less than end + interval.

A Style 3 implementation works as follows:

/// A `Strideable through` sequence currently returns the sequence of values
/// (`self`, `self + stride`, `self + stride + stride`, ... *last*) where *last*
/// is the first value in the progression **greater than or equal to** `end`.
/// There is no guarantee that `end` is an element of the sequence.

    /// Advance to the next element and return it, or `nil` if no next
    /// element exists.
    public mutating func next() -> Element? {
        if done {
            return nil
        }
        if stride > 0 ? current >= end : current <= end {
            done = true
            return current
        }
        let result = current
        current = current.advancedBy(stride)
        return result
    }
}
This solution is minimally disruptive to developers, respectful to existing code bases, and introduces a more complete semantic set of progressions that better matches progression names to developer expectations. (For example, "this argument says it goes through a value but it never even reaches that value".)

Upon adopting this change, out-of-sync strides now pass through end values:

// Unit stride
print(Array(1.stride(through: 10, by: 1)))
// prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], no change

// Old out-of-sync stride
print(Array(1.stride(through: 10, by: 8)))
// prints [1, 9]

// New out-of-sync stride
print(Array(1.stride(through: 10, by: 8)))
// prints[1, 9, 17]
There are no functional changes existing stride implementations. Only their names change.

print(Array(1.stride(towards: 10, by: 8))) // was `to`
// prints [1, 9]

print(Array(1.stride(to: 10, by: 8))) // was `through`
// prints [1, 9]
Although floating point arithmetic presents a separate and orthogonal challenge, its behavior changes if this proposal is implemented under the current generic system. For example, through now includes a value at (or at least close to) 2.0 instead of stopping at 1.9 due to accumulated floating point errors.

// Old
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]

// New
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]

// Old, does not pass through 1.9
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75]

// New, passes through 1.9
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75, 2.0]
<stride.md · GitHub on Existing Code

Renaming two stride functions and adding a third does not change or break existing code. The Swift 3 migrator can easily update the names for the two existing styles. That said, the migrator will not find in-place workarounds like a through: 2.01 epsilon adjustment to correct for floating-point fences. By adding FIXME: notes wherever through: is found and renamed to to:, the migrator could warn against continued use without a full inspection and could offer links to information about the semantic changes.

<stride.md · GitHub Considered

The only alternative at this time is "no change" to existing semantics.

···

On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff@apple.com> wrote:
I agree, splitting into two proposals is a good idea.

-Joe

Decoupling Floating Point Strides from Generic Implementations

Proposal: SE-00XX
Author(s): Erica Sadun <http://github.com/erica&gt;
Status: TBD
Review manager: TBD
Swift strides create progressions along "notionally continuous one-dimensional values" using a series of offset values. This proposal replaces the Swift's generic stride implementation with seperate algorithms for integer strides (the current implementation) and floating point strides.

This proposal was discussed on-list in the "[Discussion] stride behavior and a little bit of a call-back to digital numbers" <http://article.gmane.org/gmane.comp.lang.swift.evolution/8014&gt;thread\.

<fpstride.md · GitHub

Strideable is genericized across both integer and floating point types. A single implementation causes floating point strides to accumulate errors through repeatedly adding by intervals. Floating point types deserve their own floating point-aware implementation that minimizes errors.

<fpstride.md · GitHub Art

A Strideable to sequence returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression that is less than end.

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + tride + stride, ... last) where last is the last value in the progression less than or equal to end. There is no guarantee that end is an element of the sequence.

While floating point calls present an extremely common use-case, they use integer-style math that accumulates errors during execution. Consider this example:

let ideal = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
print(zip(Array(1.0.stride(through: 2.01, by: 0.1)), ideal).map(-))
// prints [0.0, 0.0, 2.2204460492503131e-16, 2.2204460492503131e-16,
// 4.4408920985006262e-16, 4.4408920985006262e-16, 4.4408920985006262e-16,
// 6.6613381477509392e-16, 6.6613381477509392e-16, 8.8817841970012523e-16,
// 8.8817841970012523e-16]
To create an array containing values from 1.0 to 2.0, the developer must add an epsilon value to the throughargument. Otherwise the stride progression ends near 1.9. Increasing the argument from 2.0 to 2.01 is sufficient to include the end value.
The errors in the sequence increase over time. You see this as errors become larger towards the end of the progress. This is an artifact of the generic implementation.
<fpstride.md · GitHub Design

Under the current implementation, each floating point addition in a generic stride accrues errors. The following progression never reaches 2.0.

print(Array(1.0.stride(through: 2.0, by: 0.1)))
// Prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
This same issue occurs with traditional C-style for loops. This is an artifact of floating point math, and not the specific Swift statements:

var array: [Double] =
for var i = 1.0; i <= 2.0; i += 0.1 {
    array.append(i)
}
print("Array", array)
// Prints Array [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
You should not have to manually add an epsilon to force a progression to complete.

Floating point strides are inherently dissimilar to and should not be genericized with integer strides. I propose separate their implementation, freeing them to provide their own specialized progressions, using better numeric methods. In doing so, floating point values are no longer tied to implementations that unnecessarily accrue errors or otherwise provide less-than-ideal solutions.

The following example provides a rough pass at what this might look like for floating point math. I leave specific algorithm details to experts; a decimal number solution would be more appropriate. The fun

See: RandomAscii's write-ups on all things floating point <https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition&gt;\.

import Darwin

/// A `GeneratorType` for `DoubleStrideThrough`.
public struct DoubleStrideThroughGenerator : GeneratorType {
    let start: Double
    let end: Double
    let stride: Double
    var iteration: Int = 0
    var done: Bool = false

    public init(start: Double, end: Double, stride: Double) {
        (self.start, self.end, self.stride) = (start, end, stride)
    }

    /// Advance to the next element and return it, or `nil` if no next
    /// element exists.
    public mutating func next() -> Double? {
        if done {
            return nil
        }
        let current = start + Double(iteration) * stride; iteration += 1
        if signbit(current - end) == signbit(stride) { // thanks Joe Groff
            if abs(current) > abs(end) {
                done = true
                return current
            }
            return nil
        }
        return current
    }
}

public struct DoubleStrideThrough : SequenceType {
    let start: Double
    let end: Double
    let stride: Double

    /// Return a *generator* over the elements of this *sequence*.
    ///
    /// - Complexity: O(1).
    public func generate() -> DoubleStrideThroughGenerator {
        return DoubleStrideThroughGenerator(
            start: start, end: end, stride: stride)
    }

    init(start: Double, end: Double, stride: Double) {
        _precondition(stride != 0, "stride size must not be zero")
        (self.start, self.end, self.stride) = (start, end, stride)
    }

}

public extension Double {
    public func fstride(
        through end: Double, by stride: Double
        ) -> DoubleStrideThrough {
        return DoubleStrideThrough(
            start: self, end: end, stride: stride)
    }
}
This implementation reduces floating point error by limiting accumulated additions. It uses the current Swift 2.2 through semantics (versus the revised through semantics proposed under separate cover), so it never reaches 2.0 without adding an epsilon value.

print(Array(1.0.fstride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1000000000000001, 1.2, 1.3, 1.3999999999999999,
// 1.5, 1.6000000000000001, 1.7000000000000002, 1.8,
// 1.8999999999999999]

// versus the old style
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1000000000000001, 1.2000000000000002, 1.3000000000000003,
// 1.4000000000000004, 1.5000000000000004, 1.6000000000000005,
// 1.7000000000000006, 1.8000000000000007, 1.9000000000000008]

print(zip(Array(1.0.stride(through: 2.0, by: 0.1)),
          Array(1.0.fstride(through: 2.0, by: 0.1))).map(-))
// prints [0.0, 0.0, 2.2204460492503131e-16, 2.2204460492503131e-16,
// 4.4408920985006262e-16, 4.4408920985006262e-16, 4.4408920985006262e-16,
// 4.4408920985006262e-16, 6.6613381477509392e-16, 8.8817841970012523e-16]

let ideal = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
print(zip(Array(1.0.fstride(through: 2.0, by: 0.1)), ideal).map(-))
print(zip(Array(1.0.stride(through: 2.0, by: 0.1)), ideal).map(-))

// prints
// [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.2204460492503131e-16, 0.0, 0.0]
// [0.0, 0.0, 2.2204460492503131e-16, 2.2204460492503131e-16,
// 4.4408920985006262e-16, 4.4408920985006262e-16, 4.4408920985006262e-16,
// 6.6613381477509392e-16, 6.6613381477509392e-16, 8.8817841970012523e-16]
If one were looking for a quick and dirty fix, the same kind of math used in this rough solution (let value = start + count * interval) could be adopted back into the current generic implementation.

<fpstride.md · GitHub Considered

While precision math for decimal numbers would be better addressed by introducing a decimal type and/or warnings for at-risk floating point numbers, those features lie outside the scope of this proposal.

···

On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:
I agree, splitting into two proposals is a good idea.

-Joe

It's so nice to see such care devoted to clarifying these existing
names. I agree with the premise that stride(to:by:) and
stride(through:by:) are poorly named, but I'd like to make two
critiques of this proposal--

Critique 1:

The basic distinction between the two current stride styles is that
one is inclusive and the other is exclusive of the end value. I agree
with you that "to" doesn't imply an exclusive end value, but "towards"
doesn't imply that the parameter is any sort of end value at
all--rather, it implies just a direction (or as you quote from the
NOAD, getting close or closer).

Two implications:

First, if I stride from 10 towards 0 by 1, by the plain English
meaning of the word "towards", I would expect to obtain 10, 9, 8, 7,
6, etc. If we simply rename stride(to:by:) to stride(towards:by:), I
would not get that result. By contrast, it makes sense from the
current name that stride(to:by:) attempts to increment using the `by`
parameter without considering whether the end value is greater than or
less than the start value; if you can't get from here "to" there by
such increments, too bad!

Second, if I stride from 0 towards 10 by 1 (in the English language,
not in Swift), I may or may not stop short of 10 itself. That is,
whether "towards" is inclusive or exclusive of the end value can't be
inferred from the meaning of the word; after all, if I'm making
strides towards a goal, I do intend to reach it, or at least that's
what I tell people when they ask how my PhD is going...

Generalizing from the word "towards", I don't know that any two
prepositions in the English language can be used unambiguously to
convey the distinction between inclusive and exclusive end values.
Although, on some additional thought--if I had to suggest a
preposition, perhaps "until" or "till" would be more apt than
"towards".

The saving grace of "to" and "through" in the current situation is
that the latter seems intuitively to go further than the former, and
if one deduces by analogy with the range operators that one of these
must exclude the end value and the other include it, then the two
names must mean what they do today. With three stride styles and three
prepositions, but only two range operators, this intuition is broken,
while the prepositions may not get much clearer (though I must admit
that your proposed use of "to" is an improvement).

Critique 2:

The original motivation behind your twin proposals was the epsilon
adjustment necessary for floating point end values. Your other
proposal fixes an issue with accumulated errors but doesn't solve the
need for an epsilon adjustment. Here, you propose adding a third
stride style to solve that problem, along the way shuffling the naming
of the existing stride styles. Since you haven't presented other use
cases for that third stride style here, and you haven't listed
alternatives considered for solving the original motivating problem
(i.e. epsilon adjustment), let me propose one alternative:

Keep the naming of stride styles as-is (inapt as they may be), and for
floating point end values make stride(through: aNumber, by: something)
equivalent to stride(to: theNextLargestRepresentableNumber, by:
somethingPositive) or stride(to: theNextSmallestRepresentableNumber,
by: somethingNegative). Would that solve your original issue
adequately?

Alternatively, if there are lots of examples that can be envisioned
for this third stride style, would the same examples suggest perhaps
that `..>` might be a useful third range operator?

···

On Mon, Feb 29, 2016 at 7:14 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff@apple.com> wrote:
I agree, splitting into two proposals is a good idea.

-Joe

Conventionalizing stride semantics

Proposal: SE-00NN
Author(s): Erica Sadun
Status: TBD
Review manager: TBD

Swift offers two stride functions, stride(to:, by:) and stride(through:,
by:). This proposal introduces a third style and renames the existing to and
through styles.

This proposal was discussed on-list in the "[Discussion] stride behavior and
a little bit of a call-back to digital numbers"thread.

Motivation

Strideable's function names do not semantically match the progressions they
generate. Values produced by throughdo not pass through an end point; they
stop at or before that fence. For example, 1.stride(through: 10, by: 8)
returns the progress (1, 9), not (1, 9, 17). Similarly, its to function
values reaches its end point. 1.stride(to:4, by:1) returns 1, 2, and 3. It
never makes it to 4:

The current Swift definition of to returns values in [start, end) and will
never reach end. In other words, you will never get to end.
The current Swift definition of through returns values in [start, end]. It
may never reach end and certainly never goes through that value.

Some definitions with the help of the New Oxford American Dictionary

Moving to a value expresses "a point reached at the end of a range".
To pass through a value, you should move beyond "the position or location of
something beyond or at the far end of (an opening or an obstacle)".
To move towards a value is to get "close or closer" or "getting closer to
achieving (a goal)".

Current Art

A Strideable to sequence returns the sequence of values (self, self +
stride, self + stride + stride, ... last) where last is the last value in
the progression that is less than end.

A Strideable through sequence currently returns the sequence of values
(self, self + stride, self + tride + stride, ... last) where last is the
last value in the progression less than or equal to end. There is no
guarantee that end is an element of the sequence.

The name of the calling function through suggests the progression will pass
through the end point before stopping. It does not. The name to suggests a
progression will attempt to arrive at an end point. It does not.

Detail Design

When striding to or through a number, the behavior does not match the
meaning of the word. Swift should provide three stride styles not two.

Style 1: [start, end) by interval
This style is currently called to. I propose to rename it towards as each
value works towards end. The final value in the progression is less than end

Style 2: [start, end] by interval
This style is currently called through. I propose to rename it to. The
progression concludes with a value that is less than or equal to end. Swift
provides no guarantee that end is an element of the sequence.

Style 3: [start, >=end] by interval
I propose to introduce a new style called through. The final value is
guaranteed to pass through end, either by finishing on end or past end. The
final value is strictly less than end + interval.

A Style 3 implementation works as follows:

/// A `Strideable through` sequence currently returns the sequence of values
/// (`self`, `self + stride`, `self + stride + stride`, ... *last*) where
*last*
/// is the first value in the progression **greater than or equal to**
`end`.
/// There is no guarantee that `end` is an element of the sequence.

    /// Advance to the next element and return it, or `nil` if no next
    /// element exists.
    public mutating func next() -> Element? {
        if done {
            return nil
        }
        if stride > 0 ? current >= end : current <= end {
            done = true
            return current
        }
        let result = current
        current = current.advancedBy(stride)
        return result
    }
}

This solution is minimally disruptive to developers, respectful to existing
code bases, and introduces a more complete semantic set of progressions that
better matches progression names to developer expectations. (For example,
"this argument says it goes through a value but it never even reaches that
value".)

Upon adopting this change, out-of-sync strides now pass through end values:

// Unit stride
print(Array(1.stride(through: 10, by: 1)))
// prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], no change

// Old out-of-sync stride
print(Array(1.stride(through: 10, by: 8)))
// prints [1, 9]

// New out-of-sync stride
print(Array(1.stride(through: 10, by: 8)))
// prints[1, 9, 17]

There are no functional changes existing stride implementations. Only their
names change.

print(Array(1.stride(towards: 10, by: 8))) // was `to`
// prints [1, 9]

print(Array(1.stride(to: 10, by: 8))) // was `through`
// prints [1, 9]

Although floating point arithmetic presents a separate and orthogonal
challenge, its behavior changes if this proposal is implemented under the
current generic system. For example, through now includes a value at (or at
least close to) 2.0 instead of stopping at 1.9 due to accumulated floating
point errors.

// Old
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]

// New
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]

// Old, does not pass through 1.9
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75]

// New, passes through 1.9
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75, 2.0]

Impact on Existing Code

Renaming two stride functions and adding a third does not change or break
existing code. The Swift 3 migrator can easily update the names for the two
existing styles. That said, the migrator will not find in-place workarounds
like a through: 2.01 epsilon adjustment to correct for floating-point
fences. By adding FIXME: notes wherever through: is found and renamed to
to:, the migrator could warn against continued use without a full inspection
and could offer links to information about the semantic changes.

Alternatives Considered

The only alternative at this time is "no change" to existing semantics.

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

The current usage of 'through' seems unambiguous to me. 'to' could be
improved by using 'upTo' instead.

stride(from: 1, upTo: 10, by: 1) // [1, 10)

'upTo' is already precedented in Collection.prefix(upTo:).

Dmitri

···

On Mon, Feb 29, 2016 at 5:14 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Style 1: [start, end) by interval
This style is currently called to. I propose to rename it towards as each
value works towards end. The final value in the progression is less than end

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/

Decoupling Floating Point Strides from Generic Implementations

   - Proposal: SE-00XX
   - Author(s): Erica Sadun <http://github.com/erica&gt;
   - Status: TBD
   - Review manager: TBD

Swift strides create progressions along "notionally continuous
one-dimensional values" using a series of offset values. This proposal
replaces the Swift's generic stride implementation with seperate algorithms
for integer strides (the current implementation) and floating point strides.

<snip>

If one were looking for a quick and dirty fix, the same kind of math used
in this rough solution (let value = start + count * interval) could be
adopted back into the current generic implementation.

I would rather see this solution adopted. Yes, it's inexact, but it's
about as exact as you can get with floating point numbers, and anyone
working with floating point numbers knows that you're not going to get an
exact decimal back. At least it avoids accumulated errors and guarantees
that the proper number of values will be returned, to the limits of
floating point precision.

The problem with de-generifying Strideable is that oftentimes you may start
out working with integers and then find you need to change to floats to
accommodate a new requirement. That could have very inconvenient ripple
effects if stride, arithmetic, etc. are not generic.

···

On Mon, Feb 29, 2016 at 5:16 PM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

<fpstride.md · GitHub
Considered
While precision math for decimal numbers would be better addressed by
introducing a decimal type and/or warnings for at-risk floating point
numbers, those features lie outside the scope of this proposal.

+1 for the improved floating point algorithm. I think I would be in favor of still calling it “stride” rather than “fstride”, but I could be persuaded otherwise.

There seems to be some missing text in the proposal where I’ve commented below.

—CK

I agree, splitting into two proposals is a good idea.

-Joe

Decoupling Floating Point Strides from Generic Implementations

Proposal: SE-00XX
Author(s): Erica Sadun <http://github.com/erica&gt;
Status: TBD
Review manager: TBD
Swift strides create progressions along "notionally continuous one-dimensional values" using a series of offset values. This proposal replaces the Swift's generic stride implementation with seperate algorithms for integer strides (the current implementation) and floating point strides.

This proposal was discussed on-list in the "[Discussion] stride behavior and a little bit of a call-back to digital numbers" <http://article.gmane.org/gmane.comp.lang.swift.evolution/8014&gt;thread\.

<fpstride.md · GitHub

Strideable is genericized across both integer and floating point types. A single implementation causes floating point strides to accumulate errors through repeatedly adding by intervals. Floating point types deserve their own floating point-aware implementation that minimizes errors.

<fpstride.md · GitHub Art

A Strideable to sequence returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression that is less than end.

A Strideable through sequence currently returns the sequence of values (self, self + stride, self + tride + stride, ... last) where last is the last value in the progression less than or equal to end. There is no guarantee that end is an element of the sequence.

While floating point calls present an extremely common use-case, they use integer-style math that accumulates errors during execution. Consider this example:

let ideal = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
print(zip(Array(1.0.stride(through: 2.01, by: 0.1)), ideal).map(-))
// prints [0.0, 0.0, 2.2204460492503131e-16, 2.2204460492503131e-16,
// 4.4408920985006262e-16, 4.4408920985006262e-16, 4.4408920985006262e-16,
// 6.6613381477509392e-16, 6.6613381477509392e-16, 8.8817841970012523e-16,
// 8.8817841970012523e-16]
To create an array containing values from 1.0 to 2.0, the developer must add an epsilon value to the throughargument. Otherwise the stride progression ends near 1.9. Increasing the argument from 2.0 to 2.01 is sufficient to include the end value.
The errors in the sequence increase over time. You see this as errors become larger towards the end of the progress. This is an artifact of the generic implementation.
<fpstride.md · GitHub Design

Under the current implementation, each floating point addition in a generic stride accrues errors. The following progression never reaches 2.0.

print(Array(1.0.stride(through: 2.0, by: 0.1)))
// Prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
This same issue occurs with traditional C-style for loops. This is an artifact of floating point math, and not the specific Swift statements:

var array: [Double] =
for var i = 1.0; i <= 2.0; i += 0.1 {
    array.append(i)
}
print("Array", array)
// Prints Array [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
You should not have to manually add an epsilon to force a progression to complete.

Floating point strides are inherently dissimilar to and should not be genericized with integer strides. I propose separate their implementation, freeing them to provide their own specialized progressions, using better numeric methods. In doing so, floating point values are no longer tied to implementations that unnecessarily accrue errors or otherwise provide less-than-ideal solutions.

The following example provides a rough pass at what this might look like for floating point math. I leave specific algorithm details to experts; a decimal number solution would be more appropriate. The fun

*** What fun? ;-)

···

On Feb 29, 2016, at 5:16 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

See: RandomAscii's write-ups on all things floating point <https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition&gt;\.

import Darwin

/// A `GeneratorType` for `DoubleStrideThrough`.
public struct DoubleStrideThroughGenerator : GeneratorType {
    let start: Double
    let end: Double
    let stride: Double
    var iteration: Int = 0
    var done: Bool = false

    public init(start: Double, end: Double, stride: Double) {
        (self.start, self.end, self.stride) = (start, end, stride)
    }

    /// Advance to the next element and return it, or `nil` if no next
    /// element exists.
    public mutating func next() -> Double? {
        if done {
            return nil
        }
        let current = start + Double(iteration) * stride; iteration += 1
        if signbit(current - end) == signbit(stride) { // thanks Joe Groff
            if abs(current) > abs(end) {
                done = true
                return current
            }
            return nil
        }
        return current
    }
}

public struct DoubleStrideThrough : SequenceType {
    let start: Double
    let end: Double
    let stride: Double

    /// Return a *generator* over the elements of this *sequence*.
    ///
    /// - Complexity: O(1).
    public func generate() -> DoubleStrideThroughGenerator {
        return DoubleStrideThroughGenerator(
            start: start, end: end, stride: stride)
    }

    init(start: Double, end: Double, stride: Double) {
        _precondition(stride != 0, "stride size must not be zero")
        (self.start, self.end, self.stride) = (start, end, stride)
    }

}

public extension Double {
    public func fstride(
        through end: Double, by stride: Double
        ) -> DoubleStrideThrough {
        return DoubleStrideThrough(
            start: self, end: end, stride: stride)
    }
}
This implementation reduces floating point error by limiting accumulated additions. It uses the current Swift 2.2 through semantics (versus the revised through semantics proposed under separate cover), so it never reaches 2.0 without adding an epsilon value.

print(Array(1.0.fstride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1000000000000001, 1.2, 1.3, 1.3999999999999999,
// 1.5, 1.6000000000000001, 1.7000000000000002, 1.8,
// 1.8999999999999999]

// versus the old style
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1000000000000001, 1.2000000000000002, 1.3000000000000003,
// 1.4000000000000004, 1.5000000000000004, 1.6000000000000005,
// 1.7000000000000006, 1.8000000000000007, 1.9000000000000008]

print(zip(Array(1.0.stride(through: 2.0, by: 0.1)),
          Array(1.0.fstride(through: 2.0, by: 0.1))).map(-))
// prints [0.0, 0.0, 2.2204460492503131e-16, 2.2204460492503131e-16,
// 4.4408920985006262e-16, 4.4408920985006262e-16, 4.4408920985006262e-16,
// 4.4408920985006262e-16, 6.6613381477509392e-16, 8.8817841970012523e-16]

let ideal = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
print(zip(Array(1.0.fstride(through: 2.0, by: 0.1)), ideal).map(-))
print(zip(Array(1.0.stride(through: 2.0, by: 0.1)), ideal).map(-))

// prints
// [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.2204460492503131e-16, 0.0, 0.0]
// [0.0, 0.0, 2.2204460492503131e-16, 2.2204460492503131e-16,
// 4.4408920985006262e-16, 4.4408920985006262e-16, 4.4408920985006262e-16,
// 6.6613381477509392e-16, 6.6613381477509392e-16, 8.8817841970012523e-16]
If one were looking for a quick and dirty fix, the same kind of math used in this rough solution (let value = start + count * interval) could be adopted back into the current generic implementation.

<fpstride.md · GitHub Considered

While precision math for decimal numbers would be better addressed by introducing a decimal type and/or warnings for at-risk floating point numbers, those features lie outside the scope of this proposal.

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

If one were looking for a quick and dirty fix, the same kind of math used in this rough solution (let value = start + count * interval) could be adopted back into the current generic implementation.

I notice that `start + count * interval` would actually work with `Int`, too.

I wonder if we might be better off introducing a sub-protocol of `Strideable` which can advance several increments at a time:

  protocol RandomStrideable: Strideable {
    func advancedBy(n: Self.Distance, times: Int) -> Self
  }
  
  extension RandomStrideable {
    func stride(to end: Self, by: Distance) -> RandomStrideTo<Self> { ... }
    func stride(through end: Self, by: Distance) -> RandomStrideThrough { ... }
  }

`Double` and `Int` can both conform to `RandomStrideable`; types which cannot do this (*are* there any types which cannot do this?) can conform to `Strideable`.

Alternatively, if we actually *prefer* the repeated-advance implementation and we're only using this multiplication-based one because we have to, `Strideable` can be the new multiplication-based protocol and `IncrementalStrideable` can be a subprotocol which supports repeated-advance.

···

--
Brent Royal-Gordon
Architechies

I still wonder if a better solution might involve the same syntax as ranges currently benefit from, i.e:

  0 ..< 10 // [0, 10) with an increment of 1
  (0 … 10).stride(2) // [0, 10] with an increment of 2

The most important change is that the default type for this should be able to handle higher starting indices, e.g:

  (10 … 0).stride(2) // [10, 0] with a decrement of 2

Basically I don’t like the stride global function in the first place =)

The benefit of the Range syntax is that it’s clear whether the end point is inclusive or exclusive, and it’s nice and succinct. The problem right now is just that ranges have a limit on the direction they can be traversed in for things like accessing slices of collections, in which case we’ll need to make sure that these still retain the same limitation.

···

On 1 Mar 2016, at 08:54, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

It's so nice to see such care devoted to clarifying these existing
names. I agree with the premise that stride(to:by:) and
stride(through:by:) are poorly named, but I'd like to make two
critiques of this proposal--

Critique 1:

The basic distinction between the two current stride styles is that
one is inclusive and the other is exclusive of the end value. I agree
with you that "to" doesn't imply an exclusive end value, but "towards"
doesn't imply that the parameter is any sort of end value at
all--rather, it implies just a direction (or as you quote from the
NOAD, getting close or closer).

Two implications:

First, if I stride from 10 towards 0 by 1, by the plain English
meaning of the word "towards", I would expect to obtain 10, 9, 8, 7,
6, etc. If we simply rename stride(to:by:) to stride(towards:by:), I
would not get that result. By contrast, it makes sense from the
current name that stride(to:by:) attempts to increment using the `by`
parameter without considering whether the end value is greater than or
less than the start value; if you can't get from here "to" there by
such increments, too bad!

Second, if I stride from 0 towards 10 by 1 (in the English language,
not in Swift), I may or may not stop short of 10 itself. That is,
whether "towards" is inclusive or exclusive of the end value can't be
inferred from the meaning of the word; after all, if I'm making
strides towards a goal, I do intend to reach it, or at least that's
what I tell people when they ask how my PhD is going...

Generalizing from the word "towards", I don't know that any two
prepositions in the English language can be used unambiguously to
convey the distinction between inclusive and exclusive end values.
Although, on some additional thought--if I had to suggest a
preposition, perhaps "until" or "till" would be more apt than
"towards".

The saving grace of "to" and "through" in the current situation is
that the latter seems intuitively to go further than the former, and
if one deduces by analogy with the range operators that one of these
must exclude the end value and the other include it, then the two
names must mean what they do today. With three stride styles and three
prepositions, but only two range operators, this intuition is broken,
while the prepositions may not get much clearer (though I must admit
that your proposed use of "to" is an improvement).

Critique 2:

The original motivation behind your twin proposals was the epsilon
adjustment necessary for floating point end values. Your other
proposal fixes an issue with accumulated errors but doesn't solve the
need for an epsilon adjustment. Here, you propose adding a third
stride style to solve that problem, along the way shuffling the naming
of the existing stride styles. Since you haven't presented other use
cases for that third stride style here, and you haven't listed
alternatives considered for solving the original motivating problem
(i.e. epsilon adjustment), let me propose one alternative:

Keep the naming of stride styles as-is (inapt as they may be), and for
floating point end values make stride(through: aNumber, by: something)
equivalent to stride(to: theNextLargestRepresentableNumber, by:
somethingPositive) or stride(to: theNextSmallestRepresentableNumber,
by: somethingNegative). Would that solve your original issue
adequately?

Alternatively, if there are lots of examples that can be envisioned
for this third stride style, would the same examples suggest perhaps
that `..>` might be a useful third range operator?

On Mon, Feb 29, 2016 at 7:14 PM, Erica Sadun via swift-evolution > <swift-evolution@swift.org> wrote:

On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff@apple.com> wrote:
I agree, splitting into two proposals is a good idea.

-Joe

Conventionalizing stride semantics

Proposal: SE-00NN
Author(s): Erica Sadun
Status: TBD
Review manager: TBD

Swift offers two stride functions, stride(to:, by:) and stride(through:,
by:). This proposal introduces a third style and renames the existing to and
through styles.

This proposal was discussed on-list in the "[Discussion] stride behavior and
a little bit of a call-back to digital numbers"thread.

Motivation

Strideable's function names do not semantically match the progressions they
generate. Values produced by throughdo not pass through an end point; they
stop at or before that fence. For example, 1.stride(through: 10, by: 8)
returns the progress (1, 9), not (1, 9, 17). Similarly, its to function
values reaches its end point. 1.stride(to:4, by:1) returns 1, 2, and 3. It
never makes it to 4:

The current Swift definition of to returns values in [start, end) and will
never reach end. In other words, you will never get to end.
The current Swift definition of through returns values in [start, end]. It
may never reach end and certainly never goes through that value.

Some definitions with the help of the New Oxford American Dictionary

Moving to a value expresses "a point reached at the end of a range".
To pass through a value, you should move beyond "the position or location of
something beyond or at the far end of (an opening or an obstacle)".
To move towards a value is to get "close or closer" or "getting closer to
achieving (a goal)".

Current Art

A Strideable to sequence returns the sequence of values (self, self +
stride, self + stride + stride, ... last) where last is the last value in
the progression that is less than end.

A Strideable through sequence currently returns the sequence of values
(self, self + stride, self + tride + stride, ... last) where last is the
last value in the progression less than or equal to end. There is no
guarantee that end is an element of the sequence.

The name of the calling function through suggests the progression will pass
through the end point before stopping. It does not. The name to suggests a
progression will attempt to arrive at an end point. It does not.

Detail Design

When striding to or through a number, the behavior does not match the
meaning of the word. Swift should provide three stride styles not two.

Style 1: [start, end) by interval
This style is currently called to. I propose to rename it towards as each
value works towards end. The final value in the progression is less than end

Style 2: [start, end] by interval
This style is currently called through. I propose to rename it to. The
progression concludes with a value that is less than or equal to end. Swift
provides no guarantee that end is an element of the sequence.

Style 3: [start, >=end] by interval
I propose to introduce a new style called through. The final value is
guaranteed to pass through end, either by finishing on end or past end. The
final value is strictly less than end + interval.

A Style 3 implementation works as follows:

/// A `Strideable through` sequence currently returns the sequence of values
/// (`self`, `self + stride`, `self + stride + stride`, ... *last*) where
*last*
/// is the first value in the progression **greater than or equal to**
`end`.
/// There is no guarantee that `end` is an element of the sequence.

   /// Advance to the next element and return it, or `nil` if no next
   /// element exists.
   public mutating func next() -> Element? {
       if done {
           return nil
       }
       if stride > 0 ? current >= end : current <= end {
           done = true
           return current
       }
       let result = current
       current = current.advancedBy(stride)
       return result
   }
}

This solution is minimally disruptive to developers, respectful to existing
code bases, and introduces a more complete semantic set of progressions that
better matches progression names to developer expectations. (For example,
"this argument says it goes through a value but it never even reaches that
value".)

Upon adopting this change, out-of-sync strides now pass through end values:

// Unit stride
print(Array(1.stride(through: 10, by: 1)))
// prints [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], no change

// Old out-of-sync stride
print(Array(1.stride(through: 10, by: 8)))
// prints [1, 9]

// New out-of-sync stride
print(Array(1.stride(through: 10, by: 8)))
// prints[1, 9, 17]

There are no functional changes existing stride implementations. Only their
names change.

print(Array(1.stride(towards: 10, by: 8))) // was `to`
// prints [1, 9]

print(Array(1.stride(to: 10, by: 8))) // was `through`
// prints [1, 9]

Although floating point arithmetic presents a separate and orthogonal
challenge, its behavior changes if this proposal is implemented under the
current generic system. For example, through now includes a value at (or at
least close to) 2.0 instead of stopping at 1.9 due to accumulated floating
point errors.

// Old
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]

// New
print(Array(1.0.stride(through: 2.0, by: 0.1)))
// prints [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]

// Old, does not pass through 1.9
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75]

// New, passes through 1.9
print(Array(1.0.stride(through: 1.9, by: 0.25)))
// prints [1.0, 1.25, 1.5, 1.75, 2.0]

Impact on Existing Code

Renaming two stride functions and adding a third does not change or break
existing code. The Swift 3 migrator can easily update the names for the two
existing styles. That said, the migrator will not find in-place workarounds
like a through: 2.01 epsilon adjustment to correct for floating-point
fences. By adding FIXME: notes wherever through: is found and renamed to
to:, the migrator could warn against continued use without a full inspection
and could offer links to information about the semantic changes.

Alternatives Considered

The only alternative at this time is "no change" to existing semantics.

_______________________________________________
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