[swift-evolution] [Proposal] Conventionalizing stride semantics

Howard Lovatt howard.lovatt at gmail.com
Thu Mar 3 15:37:27 CST 2016


I like (10 … 0).stride(2) including the change that (10 … 0) would be an
empty sequence and not an error.

On Friday, 4 March 2016, Dave Abrahams via swift-evolution <
swift-evolution at swift.org> wrote:

>
> on Tue Mar 01 2016, Haravikk <swift-evolution at swift.org <javascript:;>>
> 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 <javascript:;>> 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 <javascript:;>> wrote:
> >>>
> >>> On Feb 29, 2016, at 5:03 PM, Joe Groff <jgroff at apple.com
> <javascript:;>> 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 <javascript:;>
> >>> https://lists.swift.org/mailman/listinfo/swift-evolution
> >>>
> >> _______________________________________________
> >> swift-evolution mailing list
> >> swift-evolution at swift.org <javascript:;>
> >> https://lists.swift.org/mailman/listinfo/swift-evolution
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution at swift.org <javascript:;>
> > https://lists.swift.org/mailman/listinfo/swift-evolution
>
> --
> -Dave
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <javascript:;>
> https://lists.swift.org/mailman/listinfo/swift-evolution
>


-- 
-- Howard.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160304/85270eea/attachment.html>


More information about the swift-evolution mailing list