[swift-evolution] Feature proposal: Range operator with step
Dave Abrahams
dabrahams at apple.com
Thu Apr 7 13:02:25 CDT 2016
on Thu Apr 07 2016, Brent Royal-Gordon <brent-AT-architechies.com> wrote:
>>> calendar.using(.Day).stride(from: startDate, to: endDate, by: 1)
>>
>>> The `start` and `end` parameters could be grouped together into a
>>> single parameter to match `stride(over:by:)`, but you can't put the
>>> calendar or the unit into the stride—without them, there is no
>
>>> coherent way to calculate the distance between two dates.
>>>
>>> So if some types need a strider, and will need to have the method
>>> structured as `strider.stride(something:by:)`, it seems like the
> free
>>> function version for types which *don't* need a strider ought to be
>>> `stride(something:by:)`. The `something.striding(by:)` design can't
> be
>>> easily adapted to this situation.
>>
>> calendar[startDate..<endDate].striding(by: .Day)
>>
>> ?
>
> Actually, it would need to be something like
> `calendar[startDate..<endDate, unit: .Day].striding(by: 1)`, because
> NSCalendarUnit is not itself a stride, it is the *unit* of the
> stride.
Maybe:
calendar.days[startDate..<endDate].striding(by: 1)
> But even that doesn't quite help.
>
> Here's the issue.
>
> Take a look at today's Strideable. Stripped of comments and irrelevant
> annotations, you have:
>
> protocol Strideable : Comparable {
> associatedtype Stride : SignedNumberType
> func distanceTo(other: Self) -> Stride
> func advancedBy(n: Stride) -> Self
> }
>
> The problem is that there's no good way to get the calendar and unit
> into these methods. If you package them inside `Stride`, then
> `distanceTo` has no idea what calendar or unit it's supposed to
> return. If you package them inside `Self`, then `distanceTo` has two
> calendars and two units, and they might not agree (and the type system
> can't catch this issue).
>
> To fix this, you need to have a single instance which performs the
> calculation. For simple Strideable types, this instance would not do
> very much, but for dates, it would factor in the calendar and unit.
I follow all the above; it's a good explanation of the problem.
However, it doesn't explain why
calendar[startDate..<endDate, unit: .Day].striding(by: 1)
“doesn't quite help.” It seems to me that
calendar[startDate..<endDate, unit: .Day]
does factor in the calendar and unit.
> For example, suppose you modify `Strideable` so that, instead of
> applying to the values participating in the striding, it applies to
> the instance containing those values:
>
> public protocol Strideable {
> associatedtype Element: Comparable
> associatedtype Stride: SignedNumber
>
> var start: Value
> var end: Value
>
> public func distance(from earlier: Element, to later: Element) -> Stride
> public func advance(element: Element, by stride: Stride) -> Element
> }
Presumably you mean for Strideable to have a striding(by:_) method as well?
If so, how is this fundamentally different from Collection? Shouldn't
every Collection support this?
> Now, with some *really* aggressive conditional conformance work, you
> could do this:
>
> extension Range: Strideable where Element == Int {
> typealias Stride = Int
>
> public func distance(from earlier: Int, to later: Int)
> -> Int {
> return later - earlier
> }
> public func advance(element: Int, by stride: Int) {
> return value + stride
> }
> }
> extension Range: Strideable where Element == Double {
> // Ignoring the accumulation issue.
> typealias Stride = Double
>
> public func distance(from earlier: Int, to later: Int) -> Int {
> return later - earlier
> }
> public func advance(element: Double, by stride: Double) {
> return element + stride
> }
> }
Except we don't have that capability today. Instead we'd be using
overloads of ..< and ... to produce more-capable range types, c.f.
https://github.com/apple/swift/blob/swift-3-indexing-model/stdlib/public/core/Range.swift#L504
https://github.com/apple/swift/blob/swift-3-indexing-model/stdlib/public/core/Range.swift#L519
>
> // etc., for each type you care about.
> //
> // This could be automated by creating a `RangeStrideable` protocol similar to the old Strideable,
> // and having types with this automatic behavior conform to it. You could then have a single:
> // extension Range: Strideable where Element: RangeStrideable
>
> It would not be possible to stride directly over a range of NSDates,
> but you could have a "calendar range" which could be `Strideable`,
> like so:
>
> struct NSCalendarRange {
> var start: NSDate
> var end: NSDate
>
> var calendar: NSCalendar
> var unit: NSCalendarUnit
>
> var options: NSCalendarOptions
> // This mainly contains rounding options. Interestingly, NSDate is like Double in that
> // it accumulates errors through repeated addition. To fix that, I have discovered
> // a truly marvelous design which this email is too small to contain.
> }
> extension NSCalendarRange: Strideable {
> typealias Value = NSDate
> typealias Stride = Int
>
> public func distance(from earlier: NSDate, to later: NSDate) -> Int {
> let components = calendar.components(unit, from: earlier, to: later, options: options)
> return components.value(forComponent: unit)
> }
> public func advance(value: NSDate, by stride: Int) -> NSDate {
> return calendar.date(byAdding: unit, value: distance, to: value, options: options)!
> }
> }
>
> So I guess `striding(by:)` can be adapted to date arithmetic, but only
> if we adjust our conception of what a stride is striding over.
--
Dave
More information about the swift-evolution
mailing list