Please help me understand pattern matching

Hi, I don't fully understand how pattern matching works. Please help me understand what happens in this example:

struct Iter: IteratorProtocol {
    var step: (Int)->Int?
    var current: Int
    
    init(_ initial: Int, _ step: @escaping (Int)->Int?) {
        self.current = initial
        self.step = step
    }
    
    mutating func next() -> Int? {
        guard case self.current = step(self.current) else {
            print("No match. self.current == \(self.current)")
            return nil
        }
        return self.current
    }
}

var it = Iter(1, { $0 < 10 ? $0 + 1: nil })
print(it.next())

The output is:

No match. self.current == 1
nil

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.

Cheers

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?

Perhaps do something like:

 mutating func next() -> Int? {
        guard let nextValue = step(self.current) else {
            print("No match. self.current == \(self.current)")
            return nil
        }
        self.current = nextValue
        return self.current
    }

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:

https://docs.swift.org/swift-book/ReferenceManual/Patterns.html

If you look at the definition of a guard statement here: Statements — The Swift Programming Language (Swift 5.5) you can see it can be of the form

case pattern initializer 

where initializer -> = expression by Declarations — The Swift Programming Language (Swift 5.5)

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")

which prints

passed 1
passed 2
...Fatal error: fails on 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.

Baffling.

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")
}
1 Like

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.5) 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.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

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?

Yes, I believe you're right. Thanks for correcting me.

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.

1 Like
Terms of Service

Privacy Policy

Cookie Policy