[swift-evolution] [swift-evolution-announce] [Review] SE-0105: Removing Where Clauses from For-In Loops

Scott James Remnant scott at netsplit.com
Thu Jun 23 14:40:50 CDT 2016


-1

An important goal in Swift is “clarity at the point of use,” it appears in the API Design Guidelines as one of the fundamentals, but also pervades the design of the Swift language itself.

I think that removing the `where` clause from Swift’s `for` loops will reduce clarity of intent for many programmers.

It may be that `for`/`where` and `for`/`guard`/`continue` express the same result for the compiler, but an identical compiled output does not necessarily equate to an identical intent on behalf of the programmer. I do not believe that it has ever been a Swift goal that “there is only way one way to do it.”

Please consider the following example code:

  for line in lines where !line.isEmpty() {
    …
  }

The intent of this code is clear to the reader. A set of lines from a line is being iterated, and filtered such that the code within the loop is only operating on those that are not empty. This is a fairly common pattern when working with files, for example. It may be that the code within the loop would work perfectly well even in the case of empty lines, the programmer simply does not wish to consider them.

Filtered iteration is a common pattern throughout programming, other uses include things like iterating the set of “updated objects” in Core Data, etc.

Now consider the equivalent avoiding a `where` clause, and replacing it with `guard`:

  for line in lines {
    guard !line.isEmpty() else { continue }
    …
  }

To the compiler it is the same code, but to the programmer this may have a very different intent.

`guard` in Swift is _not_ simply a generic `unless` statement, the name was chosen very specifically and less harsh names rejected. `guard` belongs closer to the family of `assert` and `precondition` than it does to `if`. `guard` is used to provide an expression of pattern that *must* be true for the code following it to operate without error or other unintended side-effects. This is why the `else` block must, in some way, exit the containing scope of the `guard`.

In first example the code makes it clear, to me, that it is normal for the set of lines to contain non-empty, and empty lines. But in the second example, to me, the code makes it clear that non-empty lines are not expected in the set, and would cause the code to error if they were present; the code guards against this by stepping over them—following Postel’s Law.

My opinion would be that the correct way of preserving the intent of the first example would be either:

  for line in lines {
    if !line.isEmpty() {
      …
    }
  }

or:

  for line in lines.lazy.filter({ !$0.isEmpty() }) {
    …
  }

The second case is particularly troubling here because it’s “hard to get right” from a performance point of view. The clarity of `for`/`where` wins over all of these.

Scott


More information about the swift-evolution mailing list