[swift-evolution] [DRAFT] Regularizing Where Grammar (was Re: Add a while clause to for loops)

Brent Royal-Gordon brent at architechies.com
Fri Jun 10 14:10:22 CDT 2016

> Unlike in switch statements and do loops, a for-in loop's where-clause is separated from the pattern it modifies.

(I think "do loops" is supposed to be "do-catch statements"?)

> 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, can confuse users as to whether it modifies the expression or the pattern, and represents an inconsistency in Swift's grammar. This proposal regularizes the grammar to match other uses.

I'm definitely in favor of this. (I should be—I'm listed as coauthor.)

While I've never struggled with the `where` clause—I always assumed it was a filter—it never read right to me. This way does. When I say it out loud, "for x where x less than 10 in numbers" simply seems *far* easier to understand than "for x in numbers where x less than 10". There's something about the way the "where" combines with "for" that clarifies the entire statement.

I also think this better matches the grammar of the `case` statements in a `switch`. Erica quotes the formal grammar above, but you can actually see this in running code: in a `switch` statement, a compound case with a `where` clause like:

	case .foo, .bar where baz():

Only applies the `where` clause to the last pattern (.bar, but not .foo). That's because the rule is that the `where` belongs to the *pattern*, not the entire statement.

As a question of the proposal's drafting—as opposed to the feature being proposed—I do think that we should include an example of code before and after the change. The isOdd example ought to do.

> Note where clauses in case conditions and optional bindings have been removed in SE-0099.

I think there's actually a case to be made (no pun intended) for bringing `where` back in case conditions, but with an analogous movement of the clause's position. In other words, where (post-SE-0099) we have this production:

	case-condition → "case" pattern initializer

We would change it to:

	case-condition → "case" pattern where-clause? initializer

In use, this would look like:

	if case .some(let Point.cartesian(x, y)) where x < y = json["rect"]?["origin"].flatMap(.init(rawValue:)) { … }

Of course, the above could equally be written without a `where` clause:

	if case .some(let Point.cartesian(x, y)) = json["rect"]?["origin"].flatMap(.init(rawValue:)), x < y { … }

But nevertheless, I think it's a good idea. Why? Two reasons:

1. Consistency. If this proposal is accepted, all other `case` statements will be able to take a `where` clause in the exact same position.

2. Expressiveness. In its new position, the `where` clause is actually in the middle—not at the end—of the case condition. This makes its role much more clear: `where` in a case condition is for refining the pattern to reject things which can't quite be expressed purely as a pattern. With `where` in this position, you will not be tempted to use it for a truly unrelated condition, as you might if `where` were after the initializer.

You might be able to make an analogous argument for optional bindings, turning this:

	optional-binding-head → "let" pattern initializer

Into this:

	optional-binding-head → "let" pattern where-clause? initializer

With results like:

	if let x where x > 5 = optionalX { … }

I'm less convinced this is a good idea; there's no optional binding anywhere else in the language to be consistent with, the uses of a `where` clause are limited since an optional binding only captures one value anyway, and I don't think it makes much sense to complicate such a simple syntax.

Brent Royal-Gordon

More information about the swift-evolution mailing list