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.
Thanks for sharing!
you made your point very clear. =)