Skip to content

Instantly share code, notes, and snippets.

@erica
Last active December 11, 2018 01:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/56d533b75d0a36e3908f to your computer and use it in GitHub Desktop.
Save erica/56d533b75d0a36e3908f to your computer and use it in GitHub Desktop.

Remove C-style for-loops with conditions and incrementers

  • Proposal: SE-0007
  • Author(s): Erica Sadun
  • Status: Awaiting review (scheduled for December 7, 2015 -- December 10, 2015)
  • Review manager: Doug Gregor

Introduction

The C-style for-loop appears to be a mechanical carry-over from C rather than a genuinely Swift-specific construct. It is rarely used and not very Swift-like. More Swift-typical construction is already available with for-in statements and stride. Removing for loops would simplify the language and starve the most common use-points for -- and ++, which are already due to be eliminated from the language. The value of this construct is limited and I believe its removal should be seriously considered.

Additional Comments:

I don't believe the C-style loop was ever really part of the core Swift language philosophy. It feels like a vestigial feature that no one ever got around to cleaning up. Its entire functionality is easily replaced by other, existing Swift constructs. While Swift design deliberately held onto C-like features for familiarity, I see no special benefit to retaining the for-loop.

In contrast, consider fallthrough. There was a discussion on the evolution list Friday about fallthrough, and it quickly became apparent that losing this language feature would have a significant impact on a few key algorithms. Several list participant were able to pop forward and say "without this feature, I would not be able to do X, Y or Z". I see no parallel case to be made for for-loops.

Advantages of For Loops

Swift design supported a shallow learning curve using familiar constants and control structures. The for-loop mimics C and limits the effort needed to master this control flow. Retaining for-loops enables developers to transfer legacy code without refactoring to new control structures.

Succinctness

A list member proposes this example from a LinkedList class. It finds the right place to insert a new node.

for next = head; next != nil && index > 0; prev = next, next = next!.next, --index { }

The member adds, "Extreme? Probably, but I like it better than the same thing done in five lines of while loop."

I'd argue that readability and maintainability are core API goals. Clarity is always to be preferred to brevity. Let me point you to Justin Etheredge's essay "Don't be clever".

So next time you go to write a super clever line of code, think to yourself "Will the benefits of this super cleverness be outweighed by the future issues in maintaining and understanding the code?" And if there is anyhesitation at all, then you better not be clever, because 3 months from now you will come across that code and say "What the hell was I thinking?" Then you’ll end up rewriting it anyway.

Training Costs and Migration Benefits

The main argument against losing the feature seems to be the higher training costs for C-style coders and the higher porting costs for existing C-code. I'd argue that the training costs to a new language are significant and whether there's a C-style for loop will not materially change those overall costs, especially for those moving from Objective-C where the for-in loop is common. Second, porting to Swift should be motivated by an enhancement of safety and maintainability. Swift already supports external calls to C routines. If you want to keep your code in C, there's nothing stopping you from doing so. (Or to put it in American Politician-speak, "If you like your C-code, you can keep your C-code")

Reduced Risk from Porting

All change is traumatic, and having to refactor code involves danger. Sean Heber writes:

There are a couple of ways of solving [dangers during migration]:

  1. refactor to where loop first, then convert to Swift

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

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.

Another complaint involved forgetting or misplacing the incrementor when transforming items to while loops. There are two cases being glommed together here, and I'd like to separate them.

First, there's a collection case, in which the collection provides its own indices. In such implementations, there's simply no need to manually declare and manage an index. You can use for-in.

Second, there's what I'm going to call the bitmap case, where an index may refer to geometrically-related indices, which happens often in image processing. (I'm going to repress any "Just use Accelerate" rant. This is a significant area of programming). Here's some pseudocode that demonstrates how this might look in a for-loop-less Swift implementation:

for row in 0..<height {
    for column in 0..<width {
        var sum: UInt = 0
        for rowOffset in -1...1 {
            for columnOffset in -1...1 {
                let index = pixelOffsetAt(row + rowOffset, column + columnOffset) + redChannelOffset
                sum += pixels[index]
            }
        }
        let currentRedPixelBlurredAverage = sum / 9
        // blah blah other stuff
    }
}

Again, I don't see anything that would limit relative indexing with this proposal.

The Skip Case

Matthijs Hollemans writes,

Another benefit of a C-style for loop is that it simply ignores the loop when n <= i, as in the following example,

for var i = 100; i < n; ++i { ...

while the Swifty version gives an error because it cannot create a range where the end is smaller than the start:

for i in 100..<n { ...

Of course, you can add an if-statement to catch this but in the C-style loop this is implicit. Hence, it is more expressive.

I may be in the minority but I rather like that this becomes an error. The "skip" behavior reads to me as an unintended side-effect rather than a purposeful design goal. I prefer a philosophy that minimizes such possibilities in a safe modern language.

Support for Multiple Types

Roland King writes,

I must be the only person who still likes C-style for loops on occasion. eg a loop with something floating point

I counter that stride addresses this for all conforming strideable types, not just floating point values.

Disadvantages of For Loops

  1. Both for-in and stride provide equivalent behavior using Swift-coherent approaches without being tied to legacy terminology.
  2. There is a distinct expressive disadvantage in using for-loops compared to for-in in succinctness
  3. for-loop implementations do not lend themselves to use with collections and other core Swift types.
  4. The for-loop encourages use of unary incrementors and decrementors, which will be soon removed from the language.
  5. The semi-colon delimited declaration offers a steep learning curve from users arriving from non C-like languages
  6. If the for-loop did not exist, I doubt it would be considered for inclusion in Swift 3.

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. One side effect that was mentioned on-list was the incrementor, which is guaranteed in C-style to execute late. Late incrementor management is a feature that can be mimicked with defer, although as Tyler Fleming Cloutier points out:

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.

Proposed Approach

I suggest that the for-loop be deprecated in Swift 2.x and removed entirely in Swift 3, with coverage removed from the Swift Programming Language to match the revisions in the current 2.2 update.

Alternatives considered

Not removing for-loop from Swift, losing the opportunity to streamline the language and discard an unneeded control flow item.

Impact on existing code

A search of the Apple Swift codebase suggests this feature is rarely used. Community members of the Swift-Evolution mail list confirm that it does not feature in many pro-level apps and can be worked around for those few times when for-loops do pop up. For example:

char *blk_xor(char *dst, const char *src, size_t len)
{
 const char *sp = src;
 for (char *dp = dst; sp - src < len; sp++, dp++)
   *dp ^= *sp;
 return dst;
}

versus

func blk_xor(dst: UnsafeMutablePointer<CChar>, src:
UnsafePointer<CChar>, len: Int) -> UnsafeMutablePointer<CChar> {
   for i in 0..<len {
       dst[i] ^= src[i]
   }
   return dst
}

A search of github's Swift gists suggests the approach is used primarily by those new to the language with minimal language skills and is abandoned as language mastery is achieved.

For example:

for var i = 0 ; i < 10 ; i++ {
    print(i)
}

and

var array = [10,20,30,40,50]
for(var i=0 ; i < array.count ;i++){
    println("array[i] \(array[i])")
}

Potential alternatives

After all this discussion, let me end with a suggestion proposed by Joe Groff for anyone who would still miss the for-loop by approximating the same control flow in native Swift:

func cStyleFor(@autoclosure init initializer: () -> (), @autoclosure test: () -> Bool, @autoclosure inc: () -> (), body: () throws -> ()) rethrows {
  // left as an exercise
}

var i = 0
cStyleFor(init: i = 0, test: i < 10, inc: ++i) {
  print(i)
}

The only feature this does not include, as pointed out by list members, is a co-declared variable binding. In a C-style for-loop, the "i" is bound as part of the declaration. In this implementation, it requires a separate declaration line and remains in the scope for its lifetime.

Tyler Fleming Cloutier suggests:

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

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

Community Responses

  • "I am certainly open to considering dropping the C-style for loop. IMO, it is a rarely used feature of Swift that doesn’t carry its weight. Many of the reasons to remove them align with the rationale for removing -- and ++. " -- Chris Lattner, clattner@apple.com
  • "My intuition completely agrees that Swift no longer needs C-style for loops. We have richer, better-structured looping and functional algorithms. That said, one bit of data I’d like to see is how often C-style for loops are actually used in Swift. It’s something a quick crawl through Swift sources on GitHub could establish. If the feature feels anachronistic and is rarely used, it’s a good candidate for removal." -- Douglas Gregnor, dgregor@apple.com
  • "Every time I’ve used a C-style for loop in Swift it was because I forgot that .indices existed. If it’s removed, a fixme pointing that direction might be useful." -- David Smith, david_smith@apple.com
  • "For what it's worth we don't have a single C style for loop in the Lyft codebase." -- Keith Smiley, keithbsmiley@gmail.com
  • "Just checked; ditto Khan Academy." -- Andy Matsuchak, andy@andymatuschak.org
  • "We’ve developed a number of Swift apps for various clients over the past year and have not needed C style for loops either." -- Eric Chamberlain, eric.chamberlain@arctouch.com
  • "Every time I've tried to use a C-style for loop, I've ended up switching to a while loop because my iteration variable ended up having the wrong type (e.g. having an optional type when the value must be non-optional for the body to execute). The Postmates codebase contains no instances of C-style for loops in Swift." -- Lily Ballard
  • "I found a couple of cases of them in my codebase, but they were trivially transformed into “proper” Swift-style for loops that look better anyway. If it were a vote, I’d vote for eliminating C-style." -- Sean Heber, sean@fifthace.com
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment