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

Joseph Lord joseph at human-friendly.com
Sat Feb 27 17:59:42 CST 2016


If this happened how would legacy code be handled and a migratory written. I'm really nervous about small changes to semantics causing bugs. Would be more comfortable if the 'through' argument was renamed to force people to reconsider and check their code. It shouldn't have a migratory and the error on calling the old through variant should produce a very informative warning describing the semantic change.

Whether the change is worthwhile at all I'm not entirely sure, I haven't floating point stride much. 

Joseph

> On Feb 27, 2016, at 11:27 PM, Erica Sadun via swift-evolution <swift-evolution at 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 at 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?) as well, but this could fix an ongoing annoyance without a major change.
>> 
>> Thanks for your thoughts,
>> 
>> -- Erica
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160227/c1818230/attachment.html>


More information about the swift-evolution mailing list