[swift-evolution] [Proposal] Conventionalizing stride semantics

Dave Abrahams dabrahams at apple.com
Thu Mar 3 09:10:55 CST 2016


on Tue Mar 01 2016, Haravikk <swift-evolution at swift.org> wrote:

> 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

I like this approach a lot.  We have lots of different ways to express
variations on ranges and intervals today that don't actually involve the
range operators (including prefixTo, suffixFrom, stride).  IMO they
should.

The one problem I anticipate is that negative strides won't work well,
because forming (a...b) will have a precondition that a <= b.

> 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 at 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 at swift.org> wrote:
>>> 
>>> On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff at 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 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
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-- 
-Dave



More information about the swift-evolution mailing list