Pattern matching is a staple of functional languages. At its core, Swift is primarily an object-oriented language, like Objective-C is. However, there are many advantages to the way more functional style languages like Haskell and Erlang do things that the designers of Swift decided to include. Pattern matching in particular, saves us having to type much longer, and less readable statements to do the same checks.
I mean, which is easier to read:
case (_, 0, 0):
or
if (someTuple.1 == 0) && (someTuple.2 == 0)
They both will find the same thing, but one can be used in a switch statement, and the other has to dig into the internals of a tuple and write a much longer looking comparison to 0 for each. Not to mention the logical && there, to make sure both of them are true.
What Is a Pattern?
Now, firstly, what are we even referring to as a pattern? These don’t look like repeated decorative designs to me. I’m still pretty new to the concept in its more abstract form here, but based on what Apple’s iBook “The Swift Programming Language.” says:
A pattern represents the structure of a single value or a composite value.
That’s still pretty high level. Let’s see if Wikipedia can help a bit more:
In computer science, pattern matching is the act of checking a given sequence of tokens for the presence of the constituents of some pattern.
Okay… that’s helping me a bit. It seems that it is more talking about finding specific sequences of characters in the code to denote something. That’s still not a particularly great way of putting it, but for instance, as we’ve seen in the earlier post Tuples in Swift: Create, Read, and Return, a Tuple is constructed by having comma separated values within parentheses, like (x, y, z). The compiler should figure out by seeing that, that it is referring to a Tuple of 3 values, that are denoted as x, y, z.
In Swift, Patterns are used in variable/constant declaration, switch statement cases, catch blocks, and various conditionals (if, while, guard, and for-in statements). However, only some of these patterns can be used the declaration and for-in statements:
Pattern | Switch Cases | Conditionals | Declarations |
Wildcard | X | X | X |
Value-Binding | X | X | X |
Identifier | X | X | X |
Tuple | X | X | X |
Enumeration Case | X | X | |
Optional | X | X | |
“Is” Type-Casting | X | ||
“As” Type-Casting | X | ||
Expression | X |
Just from those names, you probably have a decent idea of what some of them are, and they may even seem familiar. We’ve covered almost all of these to SOME extent before, but not from the perspective of being a pattern.
Let’s step through each of them:
Wildcard Pattern
This one is very simple. This is the “Don’t care” style of pattern. It just ignores a value, so if we wanted to check if say, a value was on the X axis, for our (x, y, z) tuple, but we don’t care what its actual value is, we could check for it with the pattern (_, 0, 0), saying that it has 0 for its y and z components, and so any x value will match.
Value-Binding Pattern
This is one of the patterns that sounds a lot more sophisticated than what it actually is. This is, pretty much, referring to using “var” or “let” in order to declare a variable or a constant. That doesn’t mean that it isn’t useful though, particularly when used in a Switch’s case statement. There you can temporarily bind values to a variable or constant after matching it to a pattern, so a case like:
case let (x, 0, 0):
Would match for a value on the X axis, and then bind its X component to the constant named “x” so that it can be used in the case statement, like to print it, or perform a calculation.
Identifier Pattern
This one, if I understand it correctly, is even simpler than the Value-Binding Pattern. This seems to basically be the variable name itself when binding a value with the value-binding pattern, so in the above example, the “x” is the pattern, basically saying, when you find something that is (something, 0, 0), put that something in “x”. The reason it seems so intertwined, is because, as Apple says, the identifier pattern is a sub-pattern of the value-binding pattern.
Tuple Pattern
We already mentioned this somewhat, but the part that wasn’t mentioned was that a tuple isn’t just a series of comma-separated values between parenthesis, it’s actually comma-separated patterns. As such, the identifier pattern (the “x” in the tuple), was one of the patterns it was matching to within the tuple pattern itself.
Enumeration Case Pattern
This appears to be exactly what it sounds like, it matches to the value of an enumeration in any conditional statement. So if you have a cardinal direction enumeration, with the options North, South, East, and West, the enumeration case pattern is referring to how it will match to the specific enumeration value, so “case .North” will only match for the “.North” value of the enumeration. This also extends to talking about Swift Enumerations that have associated values. If that case does have an associated value, than a Tuple type of the number of elements associated with that Enumeration case must be appended to the enumeration case itself. This used to just be used in Swift’s Switch statement, but in Swift 2 was broadened to all you to test for enumeration cases in pretty much any conditional (if, while, guard, and for-in).
“Is” Type-Casting Pattern
This refers to using the “is” operator in a switch statement’s case. If the value being checked is a member of the type on the right side of the “is” operator, that pattern will be matched, and thus that case will execute. So if we were stepping through an older NSArray that might have different types in it, we could check if the value we are looking at is a String with the case:
case is String:
Then that case would be run. We wouldn’t have the value of the String though, that’s where the next pattern comes in.
“As” Type-Casting Pattern
Similarly, this is using the “as” operator in a switch statement’s case. If the value being checked is a member of the type on the right side of the operator, it will cast that value and put it into the pattern on the left side, usually a “Value-Binding” pattern. So for our above example, we could have a case statement like this:
case let text as String: print("The text we found was: \(text)")
So if it found an object that was a string, it would cast the value to a Swift String, and then Value-bind it to the “text” identifier.
Expression Pattern
This one, at least to me, is less and more complicated than it sounds, simultaneously. This refers to basically whether a Switch’s case statement matches a value (other than an enumerations available cases). So, in our earlier example, of matching a Tuple pattern with (x, 0, 0), we know that internally, the Tuple is a comma-separated list of patterns. We’ve already said that the “x” is a member of the identifier pattern. The 0’s are members of the expression pattern, so the internal patterns to our Tuple there are (Identifier, expression, expression).
A bit more concretely, the expression pattern in Swift compares values with the ” ~= ” operator, and the match succeeds if that comparison results in a “true” value. In many cases, particularly if the values compared are of the same type, it will just delegate to the more normal ” == ” operator to do the comparison. You can override the ” ~= ” operator for your own class to customize how patterns match to your type if you wish.
For instance, it is actually used when checking if an Int value is within a range of Ints. The range itself is certainly not an Integer, so we cannot just use the ” == ” operator. However, one can conceptualize the idea of checking whether an Int is within the range, so we would need another comparison to perform that kind of check (which may be just a for-in loop under the hood for all I know), and that’s where the ” ~= ” operator comes in. I don’t know if this operator has a name, but I’ll refer to it as the “equals-ish” operator.
Conclusion
Patterns in Swift are, at least to me, a strange mixture of being very powerful, simple, complex, and obvious, all at the same time. They seem to basically be codification of some of the basics of the language, in such a way as to nail down exactly what they do for the compiler, and so that they can be used in other places that could take advantage of their capabilities, like a switch’s case statement.
I’m only scratching the surface of what they can do, but particularly since many of these patterns can themselves contain patterns, that’s where the real power comes in. Combining them allows you very terse statements to express something that would normally need to be much longer, to get to a very specific combination of values.
I want this page to be a great place for people to come to understand what pattern matching in Swift means, so if you find any errors, or parts that were unclear, please let me know, so the article can be improved.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!