I was writing some code today that basically amounted to this:
struct MyStruct {
let optionalValue: String?
}
let structs: [MyStruct] = // ...
for value in structs where let unwrappedValue = value.optionalValue {
// do something with `value` and `unwrappedValue`.
}
I was surprised to find that this didn't work, that the compiler surfaced the following error on the line with the loop:
Expected expression in 'where' guard of 'for/in'
I think I had assumed that for a where clause on a for-in loop, everything that could be done in an if statement could be done in a where clause on a for-in loop. This isn't a bug or anything, because the grammar for the language does support the behavior that the compiler is exhibiting (I've copy/pasted the relevant parts of the grammar):
We can understand why the compiler is acting this way, since the grammar clearly states that a where-clause only consists of an expression, while an if-statement's condition-list can contain expression, availability-condition, case-condition, or optional-binding-condition.
Would it make sense to expand the definition of where-expression to match up with what's allowed for condition? I know that a where-clause isn't exclusively used by for-in loops, so it may be that there needs to be some other adjustment to the grammar to only allow this for for-in loops and not all places where a where-clause is possible? I'm not totally sure, and am hoping that if this seems like a good idea, some of these details can be hashed out and put into a more structured proposal.
To me it seems pretty clearly reasonable for this to be allowed. I think this is clear and easy to understand for anyone familiar with Swift code. Without this, where clauses have a real usability gap compared to an inner if / guard.
I don't see any strong counterarguments in past discussions for why this shouldn't be supported. This seems like the sort of change that (to me personally) seems like it has the potential to make it to the proposal review stage if somebody took the time to implement it.
Would it make sense to expand the definition of where-expression to match up with what's allowed for condition ?
We may want to support condition-list rather than just condition. For example, if we only support one condition, then there's no way to additional perform a boolean check after unwrapping something:
for value in structs where let string = value.string, !string.isEmpty {
// do something with `value` and `string`, which is not empty.
}
This seems reasonable to me as long as it doesn't introduce a parsing ambiguity or something. Otherwise there would still be a usability gap compared to an inner if / guard statement.
I disagree on expanding the where meaning in for cycles, which would make it inconsistent with other usages of where (expanding it in all cases would make it compete with if and the other keywords that introduce condition lists). The grammar states that the where introduces an expression, not a list of conditions.
To me, a better solution would be simply use also if in for-in, with the same exact grammar:
struct MyStruct {
let optionalValue: Bool?
}
let structs: [MyStruct] = // ...
for value in structs if let unwrappedValue = value.optionalValue, !unwrappedValue { // the `if` here introduces a list of conditions, terminated with the opening curly brance
// do something with `value` and `unwrappedValue`.
}
case _ where let id = Int(id), (40000..<50000).contains(id):
case _:
guard let id = Int(id), (40000..<50000).contains(id) else {
fallthrough
}
More than one "case _:"s yield the following warning, regardless of what you do in those cases, and fallthrough skips the logic of the next case, so this is as much a missing feature with switch as it is with for.
Case is already handled by previous patterns; consider removing it
My idea is to leave where as it is, and use if, with its existing structure of condition lists, in more places: I don't think being able to do the same exact thing in 2 completely different ways is ever going to be healthy competition in a programming language.
But I'm not sure that I agree that this would still be useful in a switch, because the point of switch is to match the value against possible patterns, not to check complex conditions in each case (that's what an if-else-if chain is for), so I would expect that each case contains a pattern.
The example you linked
case let idString:
guard let id = Int(idString), (40000..<50000).contains(id) else {
fallthrough
}
self.type = .trainStation