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
I do use for-in-while in my code. I use it *much* less frequently than simple for-in loops, but in most of those simpler cases, I’m not guarding the loop body, so it’s not really a fair comparison. For cases where I need to guard the loop body with a simple expression on the element, I much prefer the for-in-while.
Regarding your assertion that guards in the loop body are “much easier to read”, I think this is subjective. I find the for-in-while case much easier to read, mostly because it’s so much more concise than all the extra keywords and syntax necessary in the guard case (in your modulus example: 1 keyword and an expression compared to 3 keywords, an expression, a set of parens, and a set of braces).
I found a couple instances of `for in where` in our codebase: https://github.com/wordpress-mobile/WordPress-iOS/blob/7cdd33715d4c73690636e97d20e73c11d5965f05/WordPress/Classes/ViewRelated/Tools/PromptViewController.swift#L68
I think you meant “for in where” in the last paragraph?
[…] SE-0099 (via Erica Sadun): […]