The Swift Programming language has a number of powerful features that are sometimes overlooked by those new to the language. In this article, we’re going to look at one such feature – pattern matching – which offers huge potential for both simplifying your code and making it much more expressive.
Table of Contents
What is a Pattern?
In Swift, a pattern represents the structure of a single or composite value. As such, the pattern doesn’t represent just a value (although as we’ll see shortly it can do), instead it represents a range of values that match a given structure.
Ok, that might sound a little fuzzy, so let’s kick off by looking at an early example.
Later in this article, we’re going to be looking at something called a tuple pattern.
As the name suggests, the tuple pattern in Swift can be used to represent the structure of tuple values.
One example of a tuple pattern is this:
(x, y)
Notice that it doesn’t contain values. Instead it represents the structure of the tuple – a tuple containing two elements.
By representing the tuples structure rather than a tuple containing specific values, the pattern can be used to match any tuple value that contains two elements such as (20, 10)
or (11, 31)
.
In addition to being able to match values against different patterns, pattern matching in Swift also allows us to extract, all or part of any composite value that matches the pattern.
This is extremely powerful, especially when it comes to composite values such as tuples or enumerations, and allows us to pull out the different components into new constants or variables which we can then use directly within our code. We’ll look at this in more detail later in the article.
In Swift, there are eight different types of pattern that we can use in conjunction with if
statements, switch
statements, catch
clauses of do
statements or in the case conditions of if
, while
, guard
or for-in
statements:
– Identifier Patterns
– Wildcard Patterns
– Value Binding Patterns
– Tuple Patterns
– Enumeration Case Patterns
– Optional Patterns
– Type Casting Patterns
– Expressions Patterns
In addition to these eights patterns, Swift also allows us to combine some of these patterns with additional type annotations which allow us to further constrain the values those patterns will match against and along with these type annotations, we can also use additional where
clauses that provide one more more boolean expressions which must all evaluate to true
for the pattern to match successfully. We’ll see more examples of this later in the article.
For now, let’s look at the first of our eight types of pattern – the identifier pattern.
The Identifier Pattern
If you’ve ever done any programming in C or Objective-C, the identifier pattern is probably going to be the most familiar pattern that we’ll look at today.
The identifier pattern, represents a concrete value – an actual value against which we can compare other values.
The most obvious example of an identifier pattern is that of matching different cases within a switch
statement:
let value = 1
switch value {
case 1:
print("Matched 1")
case 2:
print("Matched 2")
default:
print("Not matched")
}
// Matched
Here, we use the identifier patterns 1
and 2
to match our value against.
We can also use the identifier pattern within an if
statement. When used with the if
statement, the the pattern comes first then an assignment operator and then the value we’re attempting to match:
let a = 10
if case 10 = a {
print(a)
}
// 10
It’s a bit counter intuitive to have the value we’re matching against after the assignment operator but it’s similar to the optional unwrapping that we’ve seen in previous posts.
Now, I admit, the distinction between an identifier pattern and an actual value is more conceptual than anything else, but for now try to remember that these literal values provide a pattern against which we match values. It’ll become clearer with the other patterns we’ll look at shortly.
Now, Swift’s pattern matching doesn’t stop there. As I hinted at in the introduction, Swift’s pattern matching also allows us to go beyond these simple identifier patterns and use patterns that can match more than one value. The first of these is the wildcard pattern.
The Wildcard Pattern
The wildcard pattern is the most permissive of pattern types that are available in Swift.
The wildcard pattern is represented by the underscore character (_
), and matches any value that is compared against it, including a value of nil
.
Generally, we use the wildcard pattern when we don’t care about the values that are being matched.
For example, say we wanted to multiply the value 3
by itself 3
times (the equivalent of 3^4). We don’t care about each value in the loop but we do want the operation to be performed the specified number of times:
var x = 3
for _ in 1...3 {
x *= 3
}
print(x)
// 81
The wildcard pattern here essentially acts as a placeholder. A pointer to Swift to simply match (and discard) any value it is compared against.
We can also use it with the if
statement as follows:
let a = 10
if case _ = a {
print(a) // Will *always* print the value of `a`
}
// 10
Though in this situation, the compiler will likely warn you that the condition will always be true.
So as we’ve seen the wildcard pattern can be used in many situations where we don’t particularly care about the values that are being matched and as you’ll see shortly, it can also be combined with many of the other patterns in our list such as when matching tuples or enumerations. We’ll look at these shortly but for now, let’s look at the next type of pattern the tuple pattern.
The Tuple Pattern
We took a brief look at the tuple pattern back in the introduction. As a refresher, the tuple pattern is a comma separated list of zero or more other patterns enclosed within a set of parentheses.
We use the tuple pattern to match, unsurprisingly, tuple values that have the same number of elements of the same types.
The tuple pattern can in turn contain any of the other patterns we’ll talk about today, with each contained pattern matching an individual element within the matching tuple. Only if all the individual elements of the tuple successfully match does the overall tuple match.
For example, we could create a tuple pattern that contains two identifier patterns. This pattern would match a tuple with two elements where those elements where of the same type as specified in the pattern and their values also matched.
For example, in the code below, the first case of the switch
statement will only match those tuples where the first element as an Int
value of 2
and the second element is a String
with a value of Hello
:
switch (2, "Hello") {
case (1, "World"):
print("Matched (1, World)")
case (2, "Hello"):
print("Matched (2, Hello) ")
default:
print("Didn't match.")
}
// Matched (2, Hello)
Alternatively, we can also make use of the wildcard pattern we just looked at to perform a slightly looser matching.
In this next example, the first case of the switch
statement will match tuples with two-element tuples where the second element is a String
and has the value Hello
. Due to the wildcard pattern for the first element, any value for that element would match:
let myTuples = [(2, "Hello"), (3, "World"), (1, "Hello")]
for tuple in myTuples {
switch tuple {
case (_, "Hello"):
print(tuple.0)
default:
print("Didn't match")
}
// 2
// Didn't Match
// 1
One thing to note is that if we use a tuple pattern that contains a single pattern, the parentheses around the tuple pattern have no effect. It’s the equivalent of providing a single pattern on it’s own:
switch 10 {
case (10):
print("Matched")
default:
print("Not matched.")
}
// Matched
Now, so far, we’ve seen all these patterns used in conjunction with Swift’s switch
statement, but as I mentioned at the start of the article, patterns in Swift, can also be used with many other statements such as the if
, while
, guard
or for-in
statements and the tuple pattern is no exception.
For example, we can use the tuple pattern in an if
statement:
let myTuple = (50, 20)
if case (50, 20) = myTuple {
print("\(myTuple.0), \(myTuple.1)")
}
// 50, 20
Or as part of a constant declarations:
let (month, days) : (String, Int) = ("Jan", 31)
Here I’ve combined it with an additional type annotation (the (String, Int)
to constrain the tuple pattern to only match two element tuples where the elements are a String
and an Int
.
We can also use it within a for-in
loop:
let myTuples = [(2, "Hello"), (3, "World"), (1, "Hello")]
for case (_, "Hello") in myTuples {
print("Matched")
}
// Matched
// Matched
One thing to mention in these last two examples though is that there are a few caveats about the types of patten that we can use when using a tuple pattern in either a for-in
statement or as part of a constant or variable declaration.
When used in a for-in
statement or as part of the declaration of a variable or constant, the tuple pattern can only contain other wildcard, identifier, tuple or optional patterns (we’ll look at the optional pattern shortly). In reality, this isn’t a big deal but it’s something to watch out for.
Ok, let’s have a look at the next type of Swift pattern, the value-binding pattern.
The Value-Binding Pattern
The value binding pattern in Swift, allows us to simultaneously match and bind matched values to new constants or variables all within a single statement.
This can be particularly useful when working with composite values such as tuples as it allows us to decompose those values into individual constants or variables that we can then use directly.
In similar fashion to when we individually declare constants or variables, when we use the value-binding pattern to bind a matched value to a constant, we start the pattern with the let
keyword and when binding to a variable, we use the var
keyword. Let’s have a look at an example.
Say we wanted to match a tuple value (using the tuple pattern we just looked at) but also wanted to decompose any matched tuple by binding the elements from the matched tuple to new constants we could write:
switch (40, 2) {
case (let x, let y):
print("\(x) \(y)")
}
// 40 2
Here we use the let
keyword to indicate that we are binding the matched elements to new constants.
Alternatively, we could also write the let
keyword before the tuple pattern like this:
switch (40, 2) {
case let (x, y):
print("\(x) \(y)")
}
// 40 2
These two forms are actually semantically identical. In this second case the let
keyword is said to distribute to each identifier pattern in the tuple pattern (x, y)
. The result is that the switch
cases let (x, y)
and (let x, let y)
actually equivalent and match the same values.
We can also mix things up even further.
Say we only cared about the second element in our tuple. We could replace one of the value binding patterns with the wildcard pattern we saw earlier to match and then ignore the first element of the tuple and bind only the second element to a new constant:
switch (40, 2) {
case let (_, y):
print(y)
}
// 2
We can also use the value binding pattern as part of an if
or guard
statement as well. Here we’ve got two examples, one where we bind the value of the constant b
to a new constant:
let b = 10
if case let c = b {
print(c)
}
// 10
And one where we bind the value of constant b
to a new variable d
:
if case var d = b {
d += 1
print(d)
}
// 11
Get the idea? Ok, that’s the wildcard pattern, the tuple pattern and the value binding pattern covered. Up next is the enumeration case pattern.
The Enumeration Case Pattern
As the name suggests, the enumeration case pattern is used to match the cases of an existing enumeration type and can be used pretty much anywhere such as in a case of a switch
statement, the catch
clause of a do
statement or the case condition of an if
, a while
, a guard
or a for-in
statement.
The syntax for the enumeration case pattern is simple. We simply use the case
keyword followed by the particular enumeration case we want to match against.
For example, say we had an enumeration that represented the orientation of a particular object and our enumeration had two cases, either FaceUp
or FaceDown
. Also imagine that the FaceDown
case also had an associated integer value (I’ve no idea what the integer value represents but go with it for now):
enum Orientation {
case FaceUp
case FaceDown(Int)
}
We could use the enumeration case pattern within an if statement to print out whether the object was face up or face down:
let myEnum = Orientation.FaceUp
if case Orientation.FaceUp = myEnum {
print("Face Up")
} else {
print("Face Down")
}
// Face Up
The enumeration case pattern can also do more than simply matching a particular case.
Where the enumeration case has an associated value, the enumeration case pattern can also be used in conjunction with a tuple pattern to match cases with associated values.
In fact if we are trying to match an enumeration case that does have an associated value, we must specify a tuple pattern that in turn contains one pattern for each of the enumeration cases associated values.
Here I’ve simply used a tuple pattern containing a single identifier pattern:
let myOtherEnum = Orientation.FaceDown(10)
if case Orientation.FaceDown(10) = myOtherEnum {
print("Face Down and associated value matched")
} else {
print("Didn't match")
}
// Face Down and associated value matched.
But you could just as easily use multiple patterns if the enumeration case had multiple associated values.
Instead of simply supplying an identifier pattern within the tuple pattern, we can also supply a value binding pattern. This allows us to not only match, but also to bind the associated value of the enumeration case to a new constant or variable:
let otherEnum = Orientation.FaceDown(10)
if case let Orientation.FaceDown(x) = otherEnum {
print(x)
}
// 10
if case var Orientation.FaceDown(x) = otherEnum {
x += 1
print(x)
}
// 11
This also works in exactly the same way in a switch
statement:
enum Direction {
case North(km: Int)
case East(km: Int)
case West(km: Int)
case South(km: Int)
}
let directions : [Direction] = [
.North(km:10),
.South(km:3),
.East(km:1),
.West(km:6)]
for direction in directions {
switch direction {
case let .North(x):
print("Head north for \(x)km")
case let .East(x):
print("Head east for \(x)km")
case let .South(x):
print("Head south for \(x)km")
case let .West(x):
print("Head west for \(x)km")
}
}
// Head north for 10km
// Head south for 3km
// Head east for 1km
// Head west for 6km
Note here that in the case statements I have been able to use the short-hand for referring to the enumerations case as Swift’s type inference can infer the type of each case as being the same type as the direction
.
As a final example, we can also use the wildcard pattern to ignore the associated value of an enumeration case if we are not particularly interested:
enum myEnum {
case firstValue(String)
case secondValue
}
var myVar = myEnum.firstValue("Hello")
switch myVar {
case .firstValue(_):
print("It matched.")
case .secondValue:
break
}
// It matched.
Ok. That’s the enumeration case pattern. Up next, the optional pattern.
The Optional Pattern
The optional pattern, as the name suggests, is used to match optional values.
The reality is, that the optional pattern, isn’t that different from the enumeration pattern that we’ve just looked at and as a result, can be used in all the same places such as the case label of a switch
statement, a catch
clause of a do
statement or in the case condition of an if
, while
, guard
or for-in
statement.
Why is this?
Well, we’ve looked at optionals in previous posts here and in this post we saw how optionals are actually enumerations under the hood.
As a quick reminder if you haven’t read the article, what it boils down to is that an optional is actually an enumeration with the following definition:
enum Optional<T> {
case None
case Some(T)
}
Where T
represents the basic type for the optional and in the case of the .Some
case, also represents the type of the associated value.
Given this definition, along with what we talked about in the previous section around enumeration patterns, you can may already be able to work out one way of testing for optional values:
let myOptional : String? = "Hello"
switch myOptional {
case let .Some(value):
print(value)
case .None:
break
}
// Hello
By switching on the optional itself, we can determine whether the value is .None
or an actual value and in this latter case we can also use a binding pattern to bind the associated value to a new constant or variable.
The optional pattern however, gives us a bit more syntactic sugar than this previous example allows us to test for a .Some(T)
case of either an Optional<T>
or ImplicitlyUnwrappedOptional<T>
enumeration using a more concise syntax.
In it’s standard form, the optional pattern consists of an identifier pattern, followed immediately by a question mark:
if case "Hello"? = myOptional {
print("Hello")
}
// Hello
We can also combine the optional pattern with the value-binding pattern to bind any non-nil
value to either a new constant or variable:
if case let welcome? = myOptional {
print(welcome)
}
// Hello
Here, the welcome message would only be printed if the value of myOptional
was not nil
.
We can also use the optional pattern when iterating over an array of optional values to only print out non-nil
values:
let optionalsArray : [Int?] = [1, 2, nil, 4, nil, 5]
for case let number? in optionalsArray {
print(number)
}
// 1
// 2
// 4
// 5
By this point, this syntax is probably starting to look a little familiar. Do you remember optional binding we looked at in a previous post? Drop the case
keyword and the question mark (?
) and we have the syntax we’re used to:
if let welcome = myOptional {
print(welcome)
}
// Hello
Anyway, as you can see, the optional pattern can be pretty useful for checking for optional patterns in a compact syntax.
Ok, let’s move on to our last two patterns, the type casting pattern and the expression pattern. Let’s look at the type casting pattern first.
The Type Casting Pattern
In Swift, type casting patterns match against the type of a value rather than the value itself.
In Swift, there are two forms of the type casting pattern:
is <type>
<pattern> as <type>
The is <type>
Type Casting Pattern
The first form of the type casting pattern in Swift is the is <type>
form. This form of the pattern can only be used within the case
of a switch
statement.
In this form, the pattern matches the runtime type (or subclass of that type) of a value against the type specified in the pattern.
For example, say we had an array containing a mix of different objects such as an NSString
, a UILabel
and a UITextField
.
Under the hood, the types of these objects each fit into a hierarchy of different class types. For reference, the class hierarchies for these types are as follows:
NSObject < NSString
NSObject < UIResponder < UIView < UILabel
NSObject < UIResponder < UIView < UIControl < UITextField
Now, given that our array contains a mix of different object types, how would we distinguish between them? After all, the properties of a UITextField
are a little different to a NSString
. Well, as you might have guessed we can use a loop and the is <type>
pattern:
let items : NSObject = [NSString(string:"Hello"), UILabel(), UITextField()]
for item in items {
switch item {
case is NSString:
print("NSString")
case is UIControl:
print("UIControl")
case is UIView:
print("UIView")
default:
print("Unknown")
}
}
// NSString
// UIView
// UIControl
So what’s going on here? Let’s walk through it.
The first time around the loop we enter the switch
statement and compare our NSString
object to each of the cases in the switch
statement. In this case, the NSString
object matches the type casting pattern in the first case (the is NSString
), so the print
statement in that case executes, printing out the word NSString
.
Next time around the loop, we have a UILabel
object. As we saw from the class hierarchy just now, UILabel
is actually a more specialised version of a UIView
so when compared to each of the cases in the switch
statement, the UILabel
instance actually matches the third case, the is UIView
case. Why is this? Well, remember, the is <type>
pattern matches the runtime type or any subclass of that type. Because the UILabel
object is a specialised version of a UIView
the type casting pattern matches, the body of the case executes and the word UIView
is printed out.
Finally for the last iteration of the loop we have our UITextField
object. For the same reason as we saw with the previous subject, this time around the loop the object matches the is UIView
pattern in the third case as the UITextField
object is also a more specialised version of a UIView
and no previous cases matched.
Get the idea? Now, let’s have a look at the other form of the type casting pattern the <pattern> as <type>
form.
The <pattern> as <type>
Type Casting Pattern
The <pattern> as <type>
form of the type casting pattern behaves in exactly the same way as we just saw with the is <type>
form but with one subtle difference.
In the case of the <pattern> as <type>
pattern, instead of discarding any matched value, the pattern actually casts the matched value to the pattern specified to the left-hand side of the as
keyword and returns the newly minted value.
For example, say I had another array containing a variety of different items. Due to Swift’s strong typing we would have to define our array to hold values of type Any
(we’ll cover more on the Any
type in future posts – for now think of it as kind of super type that allows the array to hold values of any type).
let otherItems : [Any] = ["Hello", 10, (1, 2), UIButton(), 3.14159]
Here the array contains a String
, an Int
, a tuple containing two Int
elements, a UIButton
instance and a Double
instance.
We then can again use a loop and a switch statement to distinguish between different element of the array. This time though, we make uses of the <pattern> as <type>
form of the type casting pattern, to make use of any matched value:
for item in otherItems {
switch item {
case 10 as Int:
print("An integer with value 10")
case let message as String:
print("You're message is: \(message)")
case let (firstValue, secondValue) as (Int, Int):
print("A tuple containing two Int elements: (\(firstValue), \(secondValue))")
default:
print("Unknown")
}
}
So what have we got? Well, there are a number of examples in here of how to use the <pattern> as <type>
type casting pattern so we’ll look at each of them in turn.
In the first case of the switch
statement, we construct our <pattern> as <type>
pattern from an identifier pattern (10
) and the type Int
. This, fairly obviously, results in a match for any Int
value with a value of 10
.
In the second case we have an example of composing the <pattern> as <type>
pattern from a String
type and a value binding pattern. The value binding pattern allows us to bind any matched value to a new constant (called message
in this case) and the type constraints thing to match only String
values.
Our third case is a bit more complicated.
The third case, is an example of composing the <pattern> as <type>
pattern from a tuple pattern and tuple type.
Here, the tuple type is a tuple containing two Int
values which constrains any matches to only those tuples that contain two Int
values.
We also supply a value binding tuple pattern in which we bind the different elements from any matched tuple to two new constants firstValue
and secondValue
.
Although I’ve not shown it in this example (and we’ve not talked about them here on the blog yet), this same idea can be extended even further to match more complex types as well. Any type you’ve defined in your program including your own structures or classes can be matched. Function types can be matched as well. The bottom line is that between the is <type>
and <pattern> as <type>
, we have a great way of matching values against their types rather than the values themselves.
Ok, let’s move on to the last of the Swift patterns we’re going to look at today – expression patterns.
The Expression Pattern
So, the last of our patterns for today is the expression pattern. The expression pattern is potentially the most powerful of the patterns we’ve looked at but can only be used within the case
of a switch
statement.
The source of much of this patterns power comes from the fact that the expression pattern compares a given predicate or value against a given pattern using Swift’s pattern matching operator (~=
) and will only match if the pattern matching operator returns true
.
The pattern matching operator in Swift is actually a function that takes two parameters. The first parameter is a pattern just like the patterns we’ve looked at so far in this article. That is then followed by a predicate or value to compare against that pattern. The pattern matching function then returns a Bool
value to indicate whether the predicate matches the pattern or not.
When used as part of an expression pattern in a switch
statement, the pattern matching operator actually breaks down something like this:
switch <predicate> {
case <pattern1>:
// Statements
case <pattern2>:
// Statements
//...
default:
// Statements
}
It probably looks pretty familiar by now as its the same general structure that we’ve seen with all of the other patterns we’ve looked at today have. In each case, the predicate is compared to the pattern using the pattern matching operator and if that returns true
the two are deemed to have matched.
There are actually four different implementations of the pattern matching operator that are available in Swift by default. These allow us to compare:
– Any two Equatable
types (i.e. any type conforming to Swift’s Equatable
protocol meaning that the two values can be compared using the ==
operator).
– An optional value against nil
– A value against a range
– A value against an interval
Note: We’ve not looked at the differences between the Range
and Interval
types and although they sound pretty similar, there is actually a subtle difference between how these type types are implemented under the hood. I’m not going to go into this today so for now, just think of these implementations as giving us the capability to check whether a given value is within a specified sequence of consecutive values. If it is, we have a match.
Ok, let’s have have a look at a quick example. Say I had a test score and wanted to calculate the corresponding grade, I can make use of a series of expression patterns and a switch
statement to compare the Int
score, to different ranges in order to identify the resulting grade:
let percentage = 87
var letterGrade = ""
switch percentage {
case 0..<60:
letterGrade = "E"
case 60..<70:
letterGrade = "D"
case 70..<80:
letterGrade = "C"
case 80..<90:
letterGrade = "B"
case 90...100:
letterGrade = "A"
default:
letterGrade = "Unknown"
}
print("Grade: \(letterGrade)")
// Grade: B
In each case, the predicate (our percentage
) is compared to the pattern (a Range
in this case) using the pattern matching operator (~=
). If that comparison returns true, the case is said to have matched.
Now, although these default implementations of the pattern matching operator are definitely useful, the reason expression patterns in Swift are so powerful is due to the fact that Swift allows us to actually write our own implementations of this pattern matching operator. This means that we can actually extend Swift’s pattern matching capabilities by implementing a pattern matching operator that allows us to say, compare a string to a number, or some custom type to another different custom type. To achieve this though, we first need to learn about operator overloading and that’s something we’re going to look at in a future post. For now though, there’s one more thing I wanted to touch on before we wrap up, and that’s where
clauses.
Where Clauses
As we’ve seen in this article, each of the different pattern types in Swift give us some pretty powerful mechanisms for comparing a value against a given pattern but we also have the option of adding additional logic to this pattern matching by supplying one or more boolean expressions in the form of a where
clause.
A where
clause consists of the where
keyword followed by one or more boolean expressions and allows us to attach additional logic to any of the patterns we’ve looked at today.
In doing so, it allows us to extend the basic pattern matching process we’ve looked at to include additional boolean logic that must evaluate to true
in order for the pattern matching to be successful.
For example, say we had a switch statement that we were using to match tuples values in different cases. Using our new knowledge of expression patterns from the previous section we might write something like:
let t = (40, 2)
switch t {
case let (a, 1...2):
print("Matched a:\(a) b:???")
default:
break;
}
// Matched a:40 b:???
Here I’ve used a tuple pattern containing a value-binding pattern and an expression pattern to match our two-element Int
tuple, if and only if, the second element is within the range 1...2
.
This approach however, leaves us with a bit of an issue. What if we actually wanted to use the value of the second element within our case. As written, we have no way of accessing it. We could use a simple tuple pattern with value binding but in doing so we’d lose our additional constraint. Instead though, we can solve our problem by making use of a where
clause:
let t = (40, 2)
switch t {
case let (a, b) where (1...2).contains(b):
print("Matched a:\(a) b:\(b)")
default:
break;
}
// Matched a:40 b:2
Here, the extra where
clause helps us retain our additional constraint to match only those tuples that have a second element whose value is within the range 1...2
but also allows us to use the value binding patterns we talked about earlier to bind the elements of the tuple to new constants for use within the body of our case. As you can see, where
clauses can be particularly useful at times.
Ok, that about wraps it up for today. I hope this tour through Swift’s different types of pattern matching has been useful. The reality is that pattern matching is one of the more powerful features of the Swift language and allows us to write cleaner more expressive and more powerful code with relatively little effort once you’ve got your head around the different patterns that are available and when combined with the extensibility that the expression pattern can potentially provide, the options are almost limitless.
As ever, let me know about any thoughts, questions or mistakes and if you’ve found this useful I’d appreciate it if you could share this article with your friends.