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

Erica Sadun erica at ericasadun.com
Sun Feb 28 00:03:40 CST 2016


I've updated the proposal to take this into account: https://gist.github.com/erica/03c398c06f6c47824429

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 at akkyra.com> wrote:
> 
> 
>> On Feb 27, 2016, at 5:38 PM, Erica Sadun <erica at ericasadun.com <mailto:erica at 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 at akkyra.com <mailto:crk at 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 at swift.org <mailto: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 <mailto: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? <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 at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <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/705036e6/attachment.html>


More information about the swift-evolution mailing list