[swift-dev] C-style For Loops

Brent Royal-Gordon brent at architechies.com
Sat Dec 19 15:47:42 CST 2015


>> for lat in (CGFloat(-60)...60).by(30) {
>>   print(lat)
>> }
>> 
>> for lat in (CGFloat(-60)..<60).by(30) {
>>   print(lat)
>> }
> 
> This is nice; why don't we put it in the standard library and get rid of the "stride" methods?

One practical reason is that interval.start must always be before interval.end, so this makes striding in reverse difficult.

Another is that the precedence of the interval operators forces additional parentheses, so the resulting code is usually ugly. (And making them higher-precedence than member access has its own problems, like `0..<(foo.count)`.)

Honestly, as much as we try to avoid them, I think the old free functions in Swift 1 gave us the best-looking code. Adapted to use intervals:

     for lat in stride(CGFloat(-60)…60, by: 30) {
          print(lat)
    }

Or, a little more cleanly:

     for lat: CGFloat in stride(-60…60, by: 30) {
          print(lat)
    }

If you don’t like the function syntax, one funky alternative might be to overload `+`:

     for lat: CGFloat in -60...60 + 30 {	// Meaning “increment by 30 each time"
          print(lat)
    }

Hmm. Rather than overloading, maybe we can use two recently-freed-up operators which are traditionally associated with looping...

    infix operator ++ { associativity none precedence 130 }	// 130 is looser than the interval operators
    infix operator -- { associativity none precedence 130 }

    func ++ <Bound: Strideable>(interval: ClosedInterval<Bound>, stride: Bound.Stride) -> StrideThrough<Bound> {
        // These implementations are just how they work against the current public API, of course.
        return interval.start.stride(through: interval.end, by: stride)
    }

    func ++ <Bound: Strideable>(interval: HalfOpenInterval<Bound>, stride: Bound.Stride) -> StrideTo<Bound> {
        return interval.start.stride(to: interval.end, by: stride)
    }

    func -- <Bound: Strideable>(interval: ClosedInterval<Bound>, stride: Bound.Stride) -> StrideThrough<Bound> {
        return interval.end.stride(through: interval.start, by: -stride)
    }

    func -- <Bound: Strideable>(interval: HalfOpenInterval<Bound>, stride: Bound.Stride) -> StrideTo<Bound> {
        return interval.end.stride(to: interval.start, by: -stride)
    }

Giving you:

    // Counts up from start by 30
    for lat: CGFloat in -60...60 ++ 30 {
        print(lat)
    }

    // Counts down from end by 30
    for lat: CGFloat in -60...60 -- 30 {
        print(lat)
    }

Perhaps this could even be extended to take a Bound -> Bound function as the right-hand parameter, allowing for the sort of arbitrary incrementing that some complain we've lost without C-style for:

    for lat: CGFloat in 1...60 ++ { $0 * 2 } {
        print(lat)
    }

And, of course, we could also provide unary variants which do +1 and -1:

    // Counts up from start by 1
    for lat: CGFloat in -60...60++ {
        print(lat)
    }

    // Counts down from end by 1
    for lat: CGFloat in -60...60-- {
        print(lat)
    }

-- 
Brent Royal-Gordon
Architechies



More information about the swift-dev mailing list