[swift-evolution] [Pitch] Retiring `where` from for-in loops

plx plxswift at icloud.com
Sun Jun 12 20:03:14 CDT 2016

> First, notice how your comment is related to the `where` clause but is actually sitting on top of the loop itself. Second, one of these two tests visits every element while the other doesn't, and it took me three reads before I could see that because I literally couldn't find the `where` clause the first two times I scanned through your code. This is a false "parallelism," causing the very thing that *isn't* parallel to disappear from the reader's eye. Compare instead this alternative, which also flips the boolean assertion:
> ```
> func testNaiveAncestor() {
>   for position in testPositions {
>     // the root has no ancestor
>     if position.isRootPosition { continue }
>     // (notice how I don't even have to comment that we're skipping root,
>     // because the code says it explicitly for me)
>     XCTAssertLessThan(position.naiveAncestor(), position)
>   }
> }
> ```

Fair enough; I added the comment when posting here…the original didn’t have one, because the where clause also explicitly says we’re skipping that one. 

To me the parallelism I care about is the following paraphrase:

  for testItem in allRelevantPossibilities { someBasicPropertyHolds(testItem) }
  for testItem in allRelevantPossibilities { someOtherPropertyHolds(testItem) }

…and adding the logic to skip the irrelevant option into the definition of “allRelevantPossibilities” seems to do a better job preserving the structural similarity I care about than throwing in some ad-hoc looking imperative logic into the test logic itself (even though functionally both are equivalent).

> Now, with my rewriting, the part of your test that is strictly similar to the other test looks parallel, and the one part that isn't at all similar (the skipping part) stands out explicitly and is now self-documenting code.
> So `where` here, IMHO, isn’t *clearly* clearer in `testNaiveAncestor()`, but it lets `testNaiveAncestor()` and `testNaiveSuccessor()` (etc.) be *systemically*-clearer, as it were.
> Second Pattern: relatedly, I find code is much clearer when `guard` is only-ever used for early exits and early returns. 
> There’s no requirement to *not* use `guard` and `continue` together, but if one is willing to stick to `guard` == “early exit / early return” it makes code much easier to read and audit.
> In a handful of places I have for loops that would need both `continue`-style conditionals and also early-exit conditionals; having `where` means I can stick to using `guard` for early-exits, whereas without it I’d have extra nesting or mixed “early-exit” guard and “continue” guard. 
> I don't know what to say here. The fact is that `guard` with `continue` is a documented and advertised feature of Swift; it's not simply supported by accident. Of course, you're free to choose not to use that feature at all. And it is true that, currently, `where` allows you to avoid that feature at the top of a `for` loop, but anywhere else inside the loop and you'll have to deal with extra nesting if you choose to reject `guard` with `continue`.

The same is true of `where`, no? It’s a part of the language, documented, free to use or not to use when or if you find it helpful (or not).

In the same way you seem to have with `where` after a for-in — and would prefer to not have to check for it, etc. — I have a hard time following mixed break/continue/return usage, and prefer to not have to check the body of each guard clause’s `else` to figure out the control flow.

I can understand that you find that difficulty baffling; I find your difficulties `where` baffling, but can accept they are sincere.

> IIUC, one of the motivating reasons for introducing `guard` was to solve the pyramid of doom problem. So you're rejecting the intended solution for extra nesting, at least in certain circumstances, a choice you're obviously free to make in your own code. But when it comes to designing a language for everyone, the fact that some users reject the intended solution would be grounds for re-examining that solution (i.e. `guard`), but the mantra here has always been one solution where possible and not multiple. So this certainly cannot be a justification for another feature (i.e. `where`) which only incidentally provides a substitute solution in certain situations.

Personally I found this use of `where` completely unremarkable and something I never thought about until I saw this discussion gaining momentum; I was already used to similar constructs so it just seemed useful in a familiar and thoroughly-uninteresting way.

That said, I could get behind excising it if there was serious interest in eventually building-in something like LINQ or comprehensions (etc.); `where` after a for-in is at this point a bit of an odd duckling.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160612/111d87c6/attachment.html>

More information about the swift-evolution mailing list