So this means that the guard condition is always false and the assignment to self.current never happens. I would like to understand what exactly the guard line does.
Is this documented somewhere? All the documentation I can find on pattern matching is rather superficial.
When next is called initially, it looks like it might match the value 1 to the output of the function (which is 2) - which is not a match, and thus it returns nil?
Thanks, I ended up doing exactly what you suggest in my actual code. But initially, I tried to do the assignment and the matching in one step and found that it didn't work.
I think you might be right about the match comparing 1 and 2, but I find it surprising that an assignment to an existing variable is interpreted as a comparison with the value of that variable, completely ignoring the actual assignment.
The Swift language reference lists all the different types of patterns but none of them seems to fit this particular case:
Now in your case self.current is the pattern which is an identifier pattern which matches the constant 1 The expression step(self.current) evaluates to 2 and hence you get inside the else block. The 'assignment' doesn't happen because there is no assignment - the = step(self.current) part is the expression as part of the definition of guard
For a more minimal example of the same situation consider the following code
var x = 1
guard case x = 1 else {
fatalError("fails on 1")
}
print("passed 1")
guard case 1 = x else {
fatalError("fails on 2")
}
print("passed 2")
guard case x = 2 else {
fatalError("fails on 3")
}
print("passed 3")
Thanks, I understand what you're saying. There's something I still find astonishing though. You're saying that this is an identifier pattern.
Here's the definition of identifier patterns from the Swift reference: "An identifier pattern matches any value and binds the matched value to a variable or constant name."
It's this binding that I incorrectly called an assignment. In my code (and also in your far better simplified example) there doesn't appear to be any such binding. Instead the result of the rhs expression is compared to the value of the existing variable on the left hand side.
So both of these are supposedly identifier patterns:
var x = 1
guard case x = 1 else {
fatalError("fails on 1")
}
guard case var x = 2 else {
fatalError("fails on 2")
}
In the first guard statement, the existing variable x is compared to 1 and the condition is true. No binding at all.
In the second guard statement, a new variable x is created and bound to the value 2. The condition is always true and no comparison is ever made.
This is indeed one of the less intuitive corners of Swift. It might help to think of if case and guard case as mini-versions of a switch. It’s easier to see what’s actually going on when there’s no = sign involved:
var x = 1
switch 1 {
case x:
print(x)
default:
fatalError("fails on 1")
}
switch 2 {
case var x:
print(x)
default:
fatalError("fails on 2")
}
Consider the first statement without var: I believe a key point here is that the only "pattern" is just the x part and this is an identifier pattern.
The whole thing case x = 1 is not a pattern itself, it is just part of a guard statement. Hence, there is no assignment happening, there is just a well-formed guard statement acting as expected.
The syntactic resemblance with an assignment is entirely coincidental in this case. I don't believe there are very many scenarios where you would use a guard like that.
Considering the second statement with var: again the whole statement is not a patter in itself, only the part var x is a pattern and it's a value-binding pattern, again as per Patterns — The Swift Programming Language (Swift 5.7) Notice this pattern indeed has the identifier pattern x as a sub-pattern. On the same page you can see that "Identifiers patterns within a value-binding pattern bind new named variables or constants to their matching values." This means that unlike in the case of x=2, whenever you have var x=2 or similar you actually assign 2 to the corresponding new name because the pattern itself is able to match the value 2.
x (without let or var) is an identifier pattern. It just compares the matched value against the pattern.
let x or var x is a value-binding pattern. It binds the matched value to the pattern (= assignment). In this case the new binding x shadows the existing local variable x.
A while back I posted my own personal Swift patterns cheat sheet to DevForums. It’s been a while, so I can’t guarantee that everything is still valid, but I still consult this post from time-to-time.
Correct me if I'm wrong, but I would say x (without let or var ) is more of an expression pattern. Because applying identifier pattern always results in binding some value to a local constant/variable.
I have to say the official reference is a little bit vague here.
I agree. Expression pattern makes a lot of sense. The identifier pattern is defined as binding to a variable or constant. It also says that any value matches while there is no mention of any comparison. In our examples (without let or var) there is neither a binding nor does any value match. It's a comparison that determines whether or not the pattern matches.
It's a comparison that uses the = operator. That's not a great language design decision in my view. Why does this not use the ~= operator?
It actually uses the ~= operator predefined in the standard library, which is implemented by just forwarding arguments to == (Int, Int) -> Bool. [Source code]
We can test this out in a playground.
// define our own ~= version, which shadows the predefined one
func ~=(lhs: Int, rhs: Int) -> Bool {
print("custom ~=(Int, Int) called")
return lhs == rhs
}
let value = 10
if case 10 = value {
print("matched")
}
Exactly. It's surprising that we have this operator and it gets used under the hood, but on the surface the assignment operator is used to make comparisons. This seems really inconsistent and confusing to me.