Swift: Where oh where can my where clause be

SE-0099, which was just accepted in Swift, eliminates where clauses from the condition clauses used in guard, if, and while statements. Specifically, transforms the grammar of optional binding conditions (“if lets”) and case conditions (“if cases”) to remove where clauses and limits each clause to a single case or assignment, massively simplifying the grammar.

Now, the discussion has turned to whether the while clause grammar should be fixed in for-in loops or eliminated entirely.

The Challenge

To get started, predict the outcome of the following two loops. And no, you may not peek before running the code.

print("for in")
var theArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for x in theArray where x % 2 == 1 { print (x) }

print("while")
var anArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]
while let x = anArray.popLast() where x % 2 == 1 { print(x) }

So did you predict the two outcomes correctly?

Some difficulty lies in way a where clause behaves depending on the kind of iterator being used. In for-in loops, it acts as a filter, using syntactic sugar for continue when its condition is not met. In while loops, it’s a conjoined Boolean, and will break when its condition is not met.

Where and While

I’ve had a bit of experience with people new to the language being confused as to whether where will filter or break. I do a lot of peer support. Because I think “for in where” is cool, I’ve pushed it.

This confusion is why this week someone introduced the notion of adding while as an alternative to where in for-in loops, to expand the grammar for breaking.

There are several problems with this proposed enhancement, the biggest one of all is at the end of the post. I’m saving it for the kicker. But I’ll work up towards that.

To get started, for-in loop where is deformed. Unlike in switch statements and do loops, a for-in loop’s where-clause is separated from the pattern it modifies. Brent Royal-Gordon first pointed this out and I was really surprised that I had never noticed:

for case? pattern in expression where-clause? code-block

case-item-list → pattern where-clause? | pattern where-clause? , case-item-list

catch pattern? where-clause? code-block

This separation makes the clause harder to associate with the pattern. It can confuse users as to whether it modifies the expression or the pattern, and represents an inconsistency in Swift’s grammar. The where clause really should read like this:

for case? pattern where-clause? in expression code-block

And if regularized, you’re looking at code that reads like this:

// if `where` is fixed
for x where x % 2 == 1 in 1...9 { ... }
// and if `while` is added
for x while x % 2 == 1 in 1...9 { ... }

Still not super clear.

Using Guard

I don’t think where (or while) should appear in the for-in loop declaration at all. (That may be a style/linter choice it may be a language modification, I haven’t made up my mind.) I think they are better expressed (and read) as guard conditions.

Guard conditions can continue (mimicking the current use of where) or break (introducing the recently pitched while behavior).  This limits the current situation where people new to the language expect while behavior and expect termination rather than sequence filtering.

for x in sequence {
    guard (condition) else { continue } // current where behavior
    guard condition else { break } // proposed while behavior
}

It’s much easier to read. Removing where from for-in loops (whether style guided/linted or language enforced) benefits these new users, reduces cognitive burden for all users, and enhances readability and predictability.

The Kicker

It’s really hard to do regular expression searches on Github and my go-to-source searchcode.com was impossible to deal with for these common words, so I ended up grepping through my “Cool 3rd Party Swift repos” folder and the Swift standard library source to see exactly how often “for in where” gets used.

My results:

stdlib: for-in: just over 600 (including a few false positives), for-in-where: 3:

private/StdlibUnittest/StdlibUnittest.swift.gyb: for j in instances.indices where i != j {
public/core/Algorithm.swift: for value in rest where value < minValue {
public/core/Algorithm.swift: for value in rest where value >= maxValue {

third party repos folder: for-in: about 650 (and ditto), for-in-where: 1:

Carthage/Source/CarthageKit/Algorithms.swift: for (node, var incomingEdges) in workingGraph where incomingEdges.contains(lastSource) {

If this construct isn’t being used by Swift experts, I think I can reasonably conclude that it isn’t going to be used by the Swift new developer either. Maybe it’s time to give it the boot.

Do you use “for in where” in your code? Can you do a search on your code base and let me know how often you use that compared to base “for in”? Please let me know!

4 Comments