Skip to content

Instantly share code, notes, and snippets.

@keybuk
Last active February 15, 2021 07:26
Show Gist options
  • Save keybuk/2e23e78a117c03546635b0e33bd60792 to your computer and use it in GitHub Desktop.
Save keybuk/2e23e78a117c03546635b0e33bd60792 to your computer and use it in GitHub Desktop.
Why the `where` clause in Swift's `for` loops matter

An important goal in Swift is clarity at the point of use, this appears in the API Design Guidelines as a fundamental, but also pervades the design of the Swift language itself.

I think that removing the where clause from Swift's for loops reduces clarity.

It may be that there are other ways to express the same code result, but "only one way to do it" has never been a Swift goal that I'm aware of, and an identical result does not necessarily equate to an identical intent.

Consider the following, which was actually a source of brief confusion for me while reading some of your code.

Example 1:

for line in results where line is String {
  // ...
}

Here is it obvious that we are performing a common operation of a filtered iteration of a set of results, selecting only those results which happen to be of String type. It suggests that it's very common for results to be a mixed set of types, and that we simply want to ignore the others in the code. Core Data produces a lot of these kinds of iterations, for example.

Now consider the following code that uses guard instead.

Example 2:

for line in results {
  guard let line = line as? String else { continue }
  // ...
}

You might expect this to be the same, and maybe it is in one example of code, but perhaps my intent is different.

In this second example I'm not filtering the results at all, in fact in this example the type of results is [NSString] and what I'm doing is performing a bridging cast for the code within the loop.

Removing the where clause and replacing it with guard for the first case reduces the clarity of the intent, a reader of the code unfamiliar with the API would have to check the type of results to determine whether or not it was a normal situation for the sequence to contain non-strings.

I also believe that removing the where clause would actually reduce the clarity of every guard in all code.

It is such named for a reason, and is not simply an unless statement. guard is used to provide a expression or pattern that must be true for the code following it to operate without error. This is why its block must, in some way, exit the containing scope of the statement.

Example 3a:

for line in results where !line.isEmpty() {
  // ...
}

In this code it is clear that I am iterating a set of lines, and filtering on the length of those lines, with the code within the loop only operating on those that are not empty. A fairly common pattern when working with files, for example.

It may be that the code works perfectly well even in the case of empty lines, I just don't want to consider them.

Example 3b:

for line in results {
  guard !line.isEmpty() else { continue }
  // ...
}

But in this example, by using guard, I am making a statement that it is a problem for the code if the line is empty. I can even be interpreted as making a statement that it is an error condition for empty lines to exist at all, but not one that I would assert() or otherwise bail out for; following Postel's Law for external input, perhaps.

I would strongly argue that the only correct way of preserving intent of 3a is using if.

Example 3c:

for line in results {
  if line.isEmpty() { continue }
  // ...
}

And that this has less clarity than the original example.

@amrangry
Copy link

thanks really good examples

@shonorio
Copy link

shonorio commented Jan 2, 2019

great examples, and I totally agree you about 3b.

@raonivaladares
Copy link

Thanks for sharing!
you made your point very clear. =)

@Yingyingkuo
Copy link

Thanks for sharing your examples of where clause!!!
This literally helps me a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment