C-style For Loops

There are a couple of ways of solving this issue:

1 refactor to where loop first, then convert to Swift
2 leave a fixit in Xcode that will perform while loop conversion

···

On Mon, Dec 7, 2015 at 00:04 Sean Heber via swift-evolution < swift-evolution@swift.org> wrote:

This is the exact scenario that lead to my own code base having a few
cases of C-style (all of which I've now trivially removed). Even so, I do
not believe it is worth keeping it around for this reason.

l8r
Sean

> On Dec 6, 2015, at 2:37 PM, Michel Fortin via swift-evolution < > swift-evolution@swift.org> wrote:
>
> There is actually one good reason I see in favor of keeping the C-style
for loop, and only one: ease of porting existing code to Swift.
>
> If you are porting code from another language that uses C-style for
loops, currently you can keep the loop as is while you translate all the
syntactic differences everywhere. If Swift didn't had a C-style for loop,
then you'd need to rethink the control flow at the same time you are
converting everything else, increasing the risk of errors during what is
already a delicate operation.
>
> C-style for loops are very common in code you can find everywhere
because many languages have it, and therefore there is a lot of algorithms,
sometime tricky algorithms you don't want to mess up, that rely on such
loops. Even if you had the desire to convert all of them to for-in loops in
your port, rewriting the control flow *at the same time* you are addressing
all the other porting issues is much worse than doing it in a separate step
*after* the crude port has been tested to work.
>
> In other words: you should avoid refactoring everything in one step. The
C-style for loop enables that.
>
> --
> Michel Fortin
> michel.fortin@michelf.ca
> https://michelf.ca
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Fun fact, you write (albiet with different syntax) C-style for-loops as library code:

for i in CStyle(0, {$0 < 20}, {$0 += 1}) {
// do something 20 times
}

Where CStyle is just a straightforward implementation of SequenceType. (Tuples allow for simultaneous iteration, which is usually when I end up with C-style for-loops)

I love the idea of this, but the syntax is remarkably ugly. (It’s actually even worse than you think, because inout closure parameters have to be declared with an exact type.) With a placeholder-based currying syntax, on the other hand…

  for i in CSequence(0, _ < 20, _ += 1) { … }

That’s not too shabby, is it?

  public struct CSequence <T>: SequenceType {
    private let initialValue: T
    private let testClosure: T -> Bool
    private let incrementClosure: (inout T) -> Void

    public init(_ initial: T, _ test: T -> Bool, _ increment: (inout T) -> Void) {
      initialValue = initial
      testClosure = test
      incrementClosure = increment
    }

    public func generate() -> CGenerator<T> {
      return CGenerator(self)
    }
  }

  public struct CGenerator<T>: GeneratorType {
    private let sequence: CSequence<T>
    private var value: T?
    
    private init(_ seq: CSequence<T>) {
      sequence = seq
    }
    
    public mutating func next() -> T? {
      if value != nil {
        sequence.incrementClosure(&value!)
      }
      else {
        value = sequence.initialValue
      }
      if sequence.testClosure(value!) {
        return value
      }
      else {
        return nil
      }
    }
  }

···

--
Brent Royal-Gordon
Architechies

Well, it is possible to just add an extra scope around the variable and the
loop:

do {
    var i = 0
    while i < 10 {
        print(i)
        i += 1
    }
}

—Johan

···

On Sun, Dec 6, 2015 at 10:13 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

*Lowered Readability and Maintainability*

I have aesthetic reasons for disliking the for-loop. The C-style loop is
harder to read especially for those not coming from C-style languages,
easier to mess up at edge conditions, and is commonly used for side-effects
which, in a language focused on safety, is not a feature to be encouraged.

For example, one side effect that was mentioned on-list yesterday was the
incrementor, which is guaranteed in C-style to execute late:
Roland King writes:

for var floatingThing = start ; floatingThing <= end ; floatingThing +=
delta
{
// more than a few lines of code with early escape continues
}

shows intent of the loop very clearly, start, condition and increment all
together at the top, and however you loop you always execute the increment
part of the statement. Convert that to a while(), if you have a continue in
the body, you have a good chance of not incrementing at all, or duplicating
the increment code before every continue. So you can’t always nicely turn
for into while. I second the point below about the loop variable being
local to the for as well, I also like that.

Late incrementor management is a feature that can be mimicked with defer,
as pointed out by several other list members.

Defer wouldn’t accomplish the exact same behavior because it would run if
an exception was thrown, which is not the same as the last clause of a for
loop, but perhaps is close enough.

The only other concern I would have is not being able to scope my
variables to the loop. I didn’t see it addressed, but perhaps its not very
important in the end anyway.

Something like the following might be nice to scope the variable
exclusively to the loop.

for var x = 0 while (someCondition()) {
// code
}

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

I think this is indeed an (anti-)feature of C-style loop.

`for var i = 0; i < 5; i += 1` is supposed to actually create a variable i;
in particular you can change this variable yourself or pass the reference
to it, so the implementation must work as follows:

var i = 0;
while( ...

Naturally, the closures are able to capture and modify i as well.

Or, in other words, by the definition of what C-style loop is, `for let i =
0; i < 5; i += 1` is impossible.

This magic behavior is in fact the best argument for the removal of C-style
loop, imho.

···

On Sun, Dec 6, 2015 at 10:14 PM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

That is very odd indeed.

If you add explicit capture, the issue goes away:

var handlers: [() -> Void] =

for i in 0..<5 {
    handlers.append {[i] in print(i, terminator:",") }
}

for handler in handlers {
    handler() // "0 1 2 3 4"
}

handlers =

for var i = 0; i < 5; i += 1 {
    handlers.append {[i] in print(i, terminator:",") }
}

print("")
for handler in handlers {
    handler() // was "5 5 5 5 5", now 1, 2, 3, 4, 5
}

It really seems like a bug to me rather than a feature of the for-loop

-- Erica

On Dec 6, 2015, at 12:03 PM, Kelly Gerber via swift-evolution < > swift-evolution@swift.org> wrote:

I think that the C-style *for* loop should be removed from Swift. The
scope rules for this *for* loop are wrong. Every loop sees the same
scope. This is a source of bugs if you export the loop variable name
outside the scope of the *for* statement, for example in a closure. The
following code illustrates the problem:

var handlers: [() -> ()] =

for i in 0..<5 {
    handlers.append { print(i) }
}

for handler in handlers {
    handler() // "0 1 2 3 4"
}

handlers =

for var i = 0; i < 5; i += 1 {
    handlers.append { print(i) }
}

for handler in handlers {
    handler() // "5 5 5 5 5"
}

The Swift *for-in* loop does the right thing naturally. The C-style *for* loop
does the wrong thing naturally. Removing the C-style *for* loop from
Swift will eliminate one more class of possible errors from the language.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

Cool! Thanks for updating the proposal with those concerns!

One thing though, could you possibly change

for var x = 0 while (someCondition()) {
to be
for var x = 0 while someCondition() {
in the gist?

I accidentally threw and extra set of parenthesis in there. Old habits die hard.

Tyler

···

On Dec 6, 2015, at 1:26 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

A slightly updated proposal write-up with everyone's feedback: TweakedProposal.md · GitHub

On Dec 6, 2015, at 2:13 PM, Tyler Fleming Cloutier <cloutiertyler@aol.com <mailto:cloutiertyler@aol.com>> wrote:

Lowered Readability and Maintainability

I have aesthetic reasons for disliking the for-loop. The C-style loop is harder to read especially for those not coming from C-style languages, easier to mess up at edge conditions, and is commonly used for side-effects which, in a language focused on safety, is not a feature to be encouraged.

For example, one side effect that was mentioned on-list yesterday was the incrementor, which is guaranteed in C-style to execute late:
Roland King writes:

for var floatingThing = start ; floatingThing <= end ; floatingThing += delta
{
  // more than a few lines of code with early escape continues
}

shows intent of the loop very clearly, start, condition and increment all together at the top, and however you loop you always execute the increment part of the statement. Convert that to a while(), if you have a continue in the body, you have a good chance of not incrementing at all, or duplicating the increment code before every continue. So you can’t always nicely turn for into while. I second the point below about the loop variable being local to the for as well, I also like that.

Late incrementor management is a feature that can be mimicked with defer, as pointed out by several other list members.

Defer wouldn’t accomplish the exact same behavior because it would run if an exception was thrown, which is not the same as the last clause of a for loop, but perhaps is close enough.

The only other concern I would have is not being able to scope my variables to the loop. I didn’t see it addressed, but perhaps its not very important in the end anyway.

Something like the following might be nice to scope the variable exclusively to the loop.

for var x = 0 while (someCondition()) {
  // code
}

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

This is true. Kind of an unfortunate extra level of indention though.

···

On Dec 6, 2015, at 1:27 PM, Johan Jensen <jj@johanjensen.dk> wrote:

Well, it is possible to just add an extra scope around the variable and the loop:

do {
    var i = 0
    while i < 10 {
        print(i)
        i += 1
    }
}

—Johan

On Sun, Dec 6, 2015 at 10:13 PM, Tyler Fleming Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Lowered Readability and Maintainability

I have aesthetic reasons for disliking the for-loop. The C-style loop is harder to read especially for those not coming from C-style languages, easier to mess up at edge conditions, and is commonly used for side-effects which, in a language focused on safety, is not a feature to be encouraged.

For example, one side effect that was mentioned on-list yesterday was the incrementor, which is guaranteed in C-style to execute late:
Roland King writes:

for var floatingThing = start ; floatingThing <= end ; floatingThing += delta
{
  // more than a few lines of code with early escape continues
}

shows intent of the loop very clearly, start, condition and increment all together at the top, and however you loop you always execute the increment part of the statement. Convert that to a while(), if you have a continue in the body, you have a good chance of not incrementing at all, or duplicating the increment code before every continue. So you can’t always nicely turn for into while. I second the point below about the loop variable being local to the for as well, I also like that.

Late incrementor management is a feature that can be mimicked with defer, as pointed out by several other list members.

Defer wouldn’t accomplish the exact same behavior because it would run if an exception was thrown, which is not the same as the last clause of a for loop, but perhaps is close enough.

The only other concern I would have is not being able to scope my variables to the loop. I didn’t see it addressed, but perhaps its not very important in the end anyway.

Something like the following might be nice to scope the variable exclusively to the loop.

for var x = 0 while (someCondition()) {
  // code
}

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

There are a couple of ways of solving this issue:

1 refactor to where loop first, then convert to Swift

This assumes 1) that you have a working toolchain to compile, execute, and test the original code, and 2) that the other language has a way to express the loop in a similar way to Swift, or something close enough.

2 leave a fixit in Xcode that will perform while loop conversion

As it was pointed out earlier, converting to a while loop requires duplication of the "increment" part of the loop everywhere there is a `continue` inside the loop. While you could make a fixit that correctly does this, I would argue that duplicating code shouldn't be done so lightly (someone will have to edit or maintain it afterwards). It's unclear to me what else could be done here however, as I can't think of a refactoring that would prevent code duplication. (And defer shouldn't be used here because it has a different behaviour when an exception is thrown.)

The fixit probably should also introduce a do{} block to correctly scope the loop variable and avoid clashes with the surrounding context.

It remains that even with a fixit that changes the code like this, the various differences it'll create in line count, increment expression placement, indentation, and sometime duplicated code, are going to impair your ability to compare the two loops side by side when hunting for porting bugs. Also the Swift version, the one that compiles thanks to a fixit, will simply be less readable... to the point that it'll generally be better to just do the refactoring as you port, despite the downsides.

Before fixit:

  for var i = 10; i >= 0; i -= 1 {
    if i == 5 {
      continue
    }
    for var j = 100; i >= 0; j -= 2 {
      print("\(i) \(j)")
    }
  }

After fixit:

  do {
    var i = 10
    while i >= 0 {
      if i == 5 {
        i -= 1
        continue
      }
      do {
        var j = 100;
        while j >= 0 {
          print("\(i) \(j)")
          j -= 2
        }
      }
      i -= 1
    }
  }

···

Le 6 déc. 2015 à 16:08, ilya <ilya.nikokoshev@gmail.com> a écrit :

--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca

Fun fact, you write (albiet with different syntax) C-style for-loops as library code:

for i in CStyle(0, {$0 < 20}, {$0 += 1}) {
// do something 20 times
}

Where CStyle is just a straightforward implementation of SequenceType. (Tuples allow for simultaneous iteration, which is usually when I end up with C-style for-loops)

I love the idea of this, but the syntax is remarkably ugly. (It’s actually even worse than you think, because inout closure parameters have to be declared with an exact type.) With a placeholder-based currying syntax, on the other hand…

  for i in CSequence(0, _ < 20, _ += 1) { … }

That’s not too shabby, is it?

This is terrific, Brent! Thanks for taking a stab at the implementation as well.

Does this move the needle for anyone else on removing for loops?

···

On Dec 6, 2015, at 10:18 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

  public struct CSequence <T>: SequenceType {
    private let initialValue: T
    private let testClosure: T -> Bool
    private let incrementClosure: (inout T) -> Void

    public init(_ initial: T, _ test: T -> Bool, _ increment: (inout T) -> Void) {
      initialValue = initial
      testClosure = test
      incrementClosure = increment
    }

    public func generate() -> CGenerator<T> {
      return CGenerator(self)
    }
  }

  public struct CGenerator<T>: GeneratorType {
    private let sequence: CSequence<T>
    private var value: T?
    
    private init(_ seq: CSequence<T>) {
      sequence = seq
    }
    
    public mutating func next() -> T? {
      if value != nil {
        sequence.incrementClosure(&value!)
      }
      else {
        value = sequence.initialValue
      }
      if sequence.testClosure(value!) {
        return value
      }
      else {
        return nil
      }
    }
  }

--
Brent Royal-Gordon
Architechies

As it was pointed out earlier, converting to a while loop requires duplication of the "increment" part of the loop everywhere there is a `continue` inside the loop.

This is not necessarily true. `defer` could be used at the top of the
while loop scope to increment whenever the loop turns. The after-fixit
example you gave can be written like so:

do {
  var i = 10
  while i >= 0 {
    defer { i -= 1 }
    if i == 5 {
      continue
    }
    do {
      var j = 100;
      while j >= 0 {
        print("\(i) \(j)")
        j -= 2
      }
     }
  }
}

···

On 12/6/2015 9:10 PM, Michel Fortin via swift-evolution wrote:

Le 6 déc. 2015 à 16:08, ilya <ilya.nikokoshev@gmail.com> a écrit :

There are a couple of ways of solving this issue:

1 refactor to where loop first, then convert to Swift

This assumes 1) that you have a working toolchain to compile, execute, and test the original code, and 2) that the other language has a way to express the loop in a similar way to Swift, or something close enough.

2 leave a fixit in Xcode that will perform while loop conversion

As it was pointed out earlier, converting to a while loop requires duplication of the "increment" part of the loop everywhere there is a `continue` inside the loop. While you could make a fixit that correctly does this, I would argue that duplicating code shouldn't be done so lightly (someone will have to edit or maintain it afterwards). It's unclear to me what else could be done here however, as I can't think of a refactoring that would prevent code duplication. (And defer shouldn't be used here because it has a different behaviour when an exception is thrown.)

The fixit probably should also introduce a do{} block to correctly scope the loop variable and avoid clashes with the surrounding context.

It remains that even with a fixit that changes the code like this, the various differences it'll create in line count, increment expression placement, indentation, and sometime duplicated code, are going to impair your ability to compare the two loops side by side when hunting for porting bugs. Also the Swift version, the one that compiles thanks to a fixit, will simply be less readable... to the point that it'll generally be better to just do the refactoring as you port, despite the downsides.

Before fixit:

  for var i = 10; i >= 0; i -= 1 {
    if i == 5 {
      continue
    }
    for var j = 100; i >= 0; j -= 2 {
      print("\(i) \(j)")
    }
  }

After fixit:

  do {
    var i = 10
    while i >= 0 {
      if i == 5 {
        i -= 1
        continue
      }
      do {
        var j = 100;
        while j >= 0 {
          print("\(i) \(j)")
          j -= 2
        }
      }
      i -= 1
    }
  }

It won't matter that you use defer *in this particular case* because the "i -= 1" statement has no side effects, and nothing appears like it can throw in the loop body. But you can't assume this will always be the case, so the fixit shouldn't be allowed to change the semantics like that.

(As I tried to point out in the last sentence of the paragraph you quoted.)

···

Le 6 déc. 2015 à 21:45, Kevin Lundberg <kevin@klundberg.com> a écrit :

As it was pointed out earlier, converting to a while loop requires duplication of the "increment" part of the loop everywhere there is a `continue` inside the loop.

This is not necessarily true. `defer` could be used at the top of the
while loop scope to increment whenever the loop turns. The after-fixit
example you gave can be written like so:

do {
  var i = 10
  while i >= 0 {
    defer { i -= 1 }
    if i == 5 {
      continue
    }
    do {
      var j = 100;
      while j >= 0 {
        print("\(i) \(j)")
        j -= 2
      }
    }
  }
}

--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca