C-style For Loops


(Gavin Eadie) #1

With C-style for loops to be removed from the language to general acclaim (including mine), is the following the best way to perform the required looping:

    for lat in (CGFloat(-60.0)).stride(through: +60.0, by: 30.0) {
        path.moveToPoint(CGPointMake(-180.0, lat))
        path.lineToPoint(CGPointMake(+180.0, lat))
    }

    for lon in (CGFloat(-150.0)).stride(through: +150.0, by: 30.0) {
        path.moveToPoint(CGPointMake(lon, +90.0))
        path.lineToPoint(CGPointMake(lon, -90.0))
    }

That seems a slightly cumbersome usage. Is there a better way to initialize the SequenceType I want to iterate over?


(Donnacha Oisín Kidney) #2

You can define an extension on interval types:

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

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

Which maybe gives you slightly more elegant usage:

for lat in (CGFloat(-60)...60).by(30) {
  print(lat)
}

for lat in (CGFloat(-60)..<60).by(30) {
  print(lat)
}

···

On 19 Dec 2015, at 18:59, Gavin Eadie via swift-dev <swift-dev@swift.org> wrote:

With C-style for loops to be removed from the language to general acclaim (including mine), is the following the best way to perform the required looping:

    for lat in (CGFloat(-60.0)).stride(through: +60.0, by: 30.0) {
        path.moveToPoint(CGPointMake(-180.0, lat))
        path.lineToPoint(CGPointMake(+180.0, lat))
    }

    for lon in (CGFloat(-150.0)).stride(through: +150.0, by: 30.0) {
        path.moveToPoint(CGPointMake(lon, +90.0))
        path.lineToPoint(CGPointMake(lon, -90.0))
    }

That seems a slightly cumbersome usage. Is there a better way to initialize the SequenceType I want to iterate over?

_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev


(Dave Abrahams) #3

You can define an extension on interval types:

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

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

Which maybe gives you slightly more elegant usage:

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?

···

On Dec 19, 2015, at 11:44 AM, Donnacha Oisín Kidney via swift-dev <swift-dev@swift.org> wrote:

On 19 Dec 2015, at 18:59, Gavin Eadie via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:

With C-style for loops to be removed from the language to general acclaim (including mine), is the following the best way to perform the required looping:

    for lat in (CGFloat(-60.0)).stride(through: +60.0, by: 30.0) {
        path.moveToPoint(CGPointMake(-180.0, lat))
        path.lineToPoint(CGPointMake(+180.0, lat))
    }

    for lon in (CGFloat(-150.0)).stride(through: +150.0, by: 30.0) {
        path.moveToPoint(CGPointMake(lon, +90.0))
        path.lineToPoint(CGPointMake(lon, -90.0))
    }

That seems a slightly cumbersome usage. Is there a better way to initialize the SequenceType I want to iterate over?

_______________________________________________
swift-dev mailing list
swift-dev@swift.org <mailto:swift-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-dev

_______________________________________________
swift-dev mailing list
swift-dev@swift.org <mailto:swift-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-dev

-Dave


(Brent Royal-Gordon) #4

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


(Dave Abrahams) #5

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.

We could declare that negative strides cause us to start at the end rather than at the start.

Another is that the precedence of the interval operators forces additional parentheses, so the resulting code is usually ugly.

I'm aware of that, but it seems less ugly to me than the alternative, and more importantly it keeps the range/interval EDSL more integrated.

(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)
   }

None of those syntaxes fly for me, I'm afraid. They all look like line noise (and some have precedence problems); I much prefer requiring a set of parentheses.

-Dave

···

On Dec 19, 2015, at 1:47 PM, Brent Royal-Gordon <brent@architechies.com> wrote:


(Brent Royal-Gordon) #6

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

We could declare that negative strides cause us to start at the end rather than at the start.

I've noticed in the past that the Swift standard library does not like to branch on sign. Of course, you could decide you just don't care in this particular case.

None of those syntaxes fly for me, I'm afraid. They all look like line noise (and some have precedence problems); I much prefer requiring a set of parentheses.

Even the free-function syntax?

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

One more alternative, which reads sort of backwards but has clear precedence, no line noise, and no free functions:

  for lat: CGFloat in 30.strideOver(-60...60) {
    ...
  }

···

--
Brent Royal-Gordon
Architechies


(Dave Abrahams) #7

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

We could declare that negative strides cause us to start at the end rather than at the start.

I've noticed in the past that the Swift standard library does not like to branch on sign.

Almost. We simply don't like to branch :slight_smile:

Of course, you could decide you just don't care in this particular case.

I'm willing to bet that in the vast majority of use cases, the sign of the stride can be known at compile-time and the branch can be eliminated.

None of those syntaxes fly for me, I'm afraid. They all look like line noise (and some have precedence problems); I much prefer requiring a set of parentheses.

Even the free-function syntax?

I personally liked the original free-function syntax—"stride(from: -50, to: 50, by: 9)". When we got protocol extensions, we decided we'd prefer methods everywhere except in a few special cases <https://swift.org/documentation/api-design-guidelines.html#general-conventions>, and this clearly falls outside those criteria. Then there are also people who feel very strongly that we should avoid exposing an argument label on the first argument except in a few special cases <https://swift.org/documentation/api-design-guidelines.html#parameters>.

My first reaction to "stride(-60…60, by: 30)" is that you need some preposition between the verb and the first argument, though now that you ask again I can see reading "a...b" as "from a through b".

I view the syntactic unification of -60...60 and (-60...60).by(30) as a big win, and it doesn't conflict with any of our API guidelines, which is another plus.

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

One more alternative, which reads sort of backwards but has clear precedence, no line noise, and no free functions:

  for lat: CGFloat in 30.strideOver(-60...60) {
    ...
  }

Others may differ, but asking 30 to stride over an interval really doesn't work for me, conceptually.

-Dave

···

On Dec 19, 2015, at 2:24 PM, Brent Royal-Gordon <brent@architechies.com> wrote:


(Brent Royal-Gordon) #8

I personally liked the original free-function syntax—"stride(from: -50, to: 50, by: 9)". When we got protocol extensions, we decided we'd prefer methods everywhere except in a few special cases, and this clearly falls outside those criteria.

I don't know about that—in `stride(from: -50, to: 50, by: 9)`, I don't see any particular reason to privilege the `from` over the `to`. I mean, I suppose `from` always starts the seqeunce whereas `to` doesn't always appear in it, but they still feel like peers which equally define the operation as a whole. This has definitely always bothered me about Swift 2.

Incidentally, I just thought of another issue with using negative values with normal intervals to walk backwards: What does `(10..<20).by(-2)` mean? A naive implementation would probably give you this:

  [20, 18, 16, 14, 12, 10]

It *should* mean this, which would not be useful terribly often:

  [18, 16, 14, 12, 10]

Meanwhile, you really want to be able to get this, but the types and operators for an open-start interval don't currently exist:

  [20, 18, 16, 14, 12]

I wonder if intervals might need another look, and maybe a redesign to make them more general.

···

--
Brent Royal-Gordon
Architechies


(Wallacy) #9

Curiously I was analyzing this part of the library a few minutes ago.
It will probably be necessary to extend "Range":

extension Range where Element: Strideable {
    func by(n: Element.Stride) -> StrideThrough<Element> {
        return startIndex.stride(through: endIndex, by: n)
    }
}

The first thing that came into my head was:

var rangeA = -150.0..<150 // HalfOpenInterval<Double>
var rangeB = -150.0...150 // ClosedInterval<Double>
var rangeC = -150..<150 // Range<Int>
var rangeD = -150...150 // Range<Int>

For "Range" we need to use startIndex and endIndex respectively, and with
HalfOpenInterval/ClosedInterval only start and end.

I do not know for you, but it seems inconsistent and redundant to me.

···

Em sáb, 19 de dez de 2015 às 18:39, Dave Abrahams via swift-dev < swift-dev@swift.org> escreveu:

On Dec 19, 2015, at 11:44 AM, Donnacha Oisín Kidney via swift-dev < > swift-dev@swift.org> wrote:

You can define an extension on interval types:

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

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

Which maybe gives you slightly more elegant usage:

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?

On 19 Dec 2015, at 18:59, Gavin Eadie via swift-dev <swift-dev@swift.org> > wrote:

With C-style for loops to be removed from the language to general acclaim
(including mine), is the following the best way to perform the required
looping:

    for lat in (CGFloat(-60.0)).stride(through: +60.0, by: 30.0) {
        path.moveToPoint(CGPointMake(-180.0, lat))
        path.lineToPoint(CGPointMake(+180.0, lat))
    }

    for lon in (CGFloat(-150.0)).stride(through: +150.0, by: 30.0) {
        path.moveToPoint(CGPointMake(lon, +90.0))
        path.lineToPoint(CGPointMake(lon, -90.0))
    }

That seems a slightly cumbersome usage. Is there a better way to
initialize the SequenceType I want to iterate over?
_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

-Dave

_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev


(Dave Abrahams) #10

Sorry to have left this so long; I'm just catching up.

I personally liked the original free-function syntax—"stride(from:

-50, to: 50, by: 9)". When we got protocol extensions, we decided
we'd prefer methods everywhere except in a few special cases, and this
clearly falls outside those criteria.

I don't know about that—in `stride(from: -50, to: 50, by: 9)`, I don't
see any particular reason to privilege the `from` over the `to`.

Me neither! I'm just saying, the criteria we used to decide which
things could remain non-methods would not have allowed us to keep stride
this way. We would need to have agreement on some principle that would
allow this form.

I mean, I suppose `from` always starts the seqeunce whereas `to`
doesn't always appear in it, but they still feel like peers which
equally define the operation as a whole.

Yes, but consider a.appendingContentsOf(b) or (sorry for bringing this
up) x.union(y). I, at least, don't think those should be free functions.

I think the reason stride feels different is that it's weird for a
method on a scalar value to produce something with higher
dimensionality.

This has definitely always bothered me about Swift 2.

Incidentally, I just thought of another issue with using negative
values with normal intervals to walk backwards: What does
`(10..<20).by(-2)` mean? A naive implementation would probably give
you this:

  [20, 18, 16, 14, 12, 10]

It *should* mean this, which would not be useful terribly often:

  [18, 16, 14, 12, 10]

Great point, to say nothing of (10..<19).by(-2)! (does it start at 18 or 19)?

I think it *should* mean something useful... but I don't know why
you think one is more useful than the other.

Certainly, (10..<19).by(2).reversed() is unambiguous. I think it would
have a nice symmetry if you could say that:

    r.by(-x).reversed() == r.by(x)
    r.by(x).reversed() == r.by(-x)

Would those be useful semantics in your eyes?

Meanwhile, you really want to be able to get this, but the types and
operators for an open-start interval don't currently exist:

  [20, 18, 16, 14, 12]

I don't understand why “you really want to be able to get this” from (10..<20).by(-2).

Wouldn't (12...20).by(-2) be a reasonable way to get there?

···

on Sat Dec 19 2015, Brent Royal-Gordon <brent-AT-architechies.com> wrote:

I wonder if intervals might need another look, and maybe a redesign to
make them more general.

--
-Dave


(Dave Abrahams) #11

Curiously I was analyzing this part of the library a few minutes ago.
It will probably be necessary to extend "Range":

extension Range where Element: Strideable {
    func by(n: Element.Stride) -> StrideThrough<Element> {
        return startIndex.stride(through: endIndex, by: n)
    }
}

Why?

The first thing that came into my head was:

var rangeA = -150.0..<150 // HalfOpenInterval<Double>
var rangeB = -150.0...150 // ClosedInterval<Double>
var rangeC = -150..<150 // Range<Int>
var rangeD = -150...150 // Range<Int>

For "Range" we need to use startIndex and endIndex respectively, and with HalfOpenInterval/ClosedInterval only start and end.

I do not know for you, but it seems inconsistent and redundant to me.

I don't understand most of what you're saying here, or why you're saying it. The extensions as shown in Donnacha's post work for me.

You can define an extension on interval types:

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

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

Which maybe gives you slightly more elegant usage:

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?

With C-style for loops to be removed from the language to general acclaim (including mine), is the following the best way to perform the required looping:

    for lat in (CGFloat(-60.0)).stride(through: +60.0, by: 30.0) {
        path.moveToPoint(CGPointMake(-180.0, lat))
        path.lineToPoint(CGPointMake(+180.0, lat))
    }

    for lon in (CGFloat(-150.0)).stride(through: +150.0, by: 30.0) {
        path.moveToPoint(CGPointMake(lon, +90.0))
        path.lineToPoint(CGPointMake(lon, -90.0))
    }

That seems a slightly cumbersome usage. Is there a better way to initialize the SequenceType I want to iterate over?

_______________________________________________
swift-dev mailing list
swift-dev@swift.org <mailto:swift-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-dev

_______________________________________________
swift-dev mailing list
swift-dev@swift.org <mailto:swift-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-dev

-Dave

_______________________________________________
swift-dev mailing list
swift-dev@swift.org <mailto:swift-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-dev

-Dave

···

On Dec 19, 2015, at 1:20 PM, Wallacy <wallacyf@gmail.com> wrote:
Em sáb, 19 de dez de 2015 às 18:39, Dave Abrahams via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> escreveu:

On Dec 19, 2015, at 11:44 AM, Donnacha Oisín Kidney via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:

On 19 Dec 2015, at 18:59, Gavin Eadie via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:


(Wallacy) #12

Why?

Because this works:

var rangeA = -150.0..<150
for lat in rangeA.by(30) {
    print(lat)
}

And this not:

var rangeC = -150..<150
for lat in rangeC.by(30) { // Error - Value of type 'Range<Int>' has no
member 'by'
    print(lat)
}

I think this is expected to work, or not?

If you make a extension for "Range" will work of course.

I don't understand most of what you're saying here, or why you're saying
it. The extensions as shown in Donnacha's post work for me.

Not a big deal, just saying I think weird call "startIndex" and "endIndex"
on "Range", because on (HalfOpen|Close)Interval are only "start" and "end".

Also is weird the existence of the 3 types for basically the same purpose.

But is not the point of this topic of course.


(Dave Abrahams) #13

Why?

Because this works:

var rangeA = -150.0..<150
for lat in rangeA.by(30) {
    print(lat)
}

And this not:

var rangeC = -150..<150
for lat in rangeC.by(30) { // Error - Value of type 'Range<Int>' has no
member 'by'
    print(lat)
}

I think this is expected to work, or not?

Fair enough; that makes sense.

If you make a extension for "Range" will work of course.

I don't understand most of what you're saying here, or why you're saying
it. The extensions as shown in Donnacha's post work for me.

Not a big deal, just saying I think weird call "startIndex" and "endIndex"
on "Range", because on (HalfOpen|Close)Interval are only "start" and "end".

Range is a collection, so it has to have startIndex and endIndex.
Interval does not necessarily have anything to do with indices.

Also is weird the existence of the 3 types for basically the same
purpose.

We're missing the generics features that would allow them to be unified.

···

on Sat Dec 19 2015, Wallacy <swift-dev-AT-swift.org> wrote:

--
-Dave