Drill down into pattern matching in Swift

  • 2020-06-15 10:22:00
  • OfStack

Pattern matching

Pattern matching is one of the most common programming patterns in Swift. Using pattern matching can help us write concise, clear, and readable code, making our code concise and powerful.

Pattern matching in conditional judgment

Conditional judgment is the most common process control we use. In Swift, only values of type Bool can be accepted as conditional body. In addition to directly determining the Bool value, we can also use optional binding using conditional statements, which is a very common approach in our development.

Matching enumerated values

In Swift, the enumeration type created is by default non-comparable (Comparable protocol is not implemented), which means that we cannot directly use the == operator to determine whether two enumeration values are equal, in which case we need to use pattern matching:

Create 1 enumerated type:


enum Result {
 case success
 case failure
}

Initialize 1 enumeration value:


let result = Result.success

Use pattern matching to determine the value of the enumeration value created:


if case .success = result {
 print("Value of result is success.")
}

Optional binding

Create 1 optional value:


let optionalInt: Int? = 1

Unpacking using optional bindings:


if let val = optionalInt {
 print("The value of optionalInt is (val)")
}
func handleGuard() {
 guard let val = optionalInt else {
 return
 }
 print("The value of optionalInt is (val)")
}
handleGuard()

Another mode of optional binding, which is also the most basic mode of optional binding:


if case .some(let val) = optionalInt {
 print("The value of optionalInt is (val)")
}

It can also be simplified as:


if case let val? = optionalInt {
 print("The value of optionalInt is (val)")
}

Pattern matching in a loop

The problem is that the optional binding of if let mode can only implement the binding of 1 optional value. What if we need to match the optional value in 1 array? In this case, we cannot use the form of if let, we need to use the form of if case let

Create an array of optional values:


let values: [Int?] = [1, nil, 3, nil, 5, nil, 7, nil, 9, nil]

Traversal:


for val in values {
 print("Value in values is (String(describing: val))")
}

Or:


var valuesIterator = values.makeIterator()
while let val = valuesIterator.next() {
 print("Value in values is (String(describing: val))")
}

We get all the values and optional values. If we need to filter the optional values, we can do this:


let result = Result.success
0

Doing so increases the time complexity and requires two traversals to filter out the data. We can do this using pattern matching:


for case let val? in values {
 print("Value in values is (val)")
}

Or:


valuesIterator = values.makeIterator()
while let val = valuesIterator.next(), val != nil {
 print("Value in values is (String(describing: val))")
}

So you can filter the nil value, isn't that easy? You can also use for case to match an array of enumerated values:


let result = Result.success
3

For complex enumerated types:


let result = Result.success
4

Filter http values:


let result = Result.success
5

for repeats the where clause

In addition, we can also follow an where clause after the for loop to perform pattern matching:


let result = Result.success
6

Query all Numbers divisible by 3 in an array:


let rangeValues = Array(0...999)
for threeDivideValue in rangeValues where threeDivideValue % 3 == 0 {
 print("Three devide value: (threeDivideValue)")
}

Query all Numbers containing 3:


for containsThree in rangeValues where String(containsThree).contains("3") {
 print("Value contains three: (containsThree)")
}

Pattern matching in Switch

Pattern matching in Switch is also very common, and the proper use of pattern matching in Switch can bring many benefits by making our code cleaner, reducing the amount of code and increasing development efficiency.

Range match


let result = Result.success
9

Match tuple types

Create 1 tuple type:


let tuples: (Int, String) = (httpCode: 404, status: "Not Found.")

Match:


switch tuples {
case (400..., let status):
 print("The http code is 40x, http status is (status)")
default: break
}

Create 1 point:


let somePoint = (1, 1)

Match:


switch somePoint {
case (0, 0):
 print("(somePoint) is at the origin")
case (_, 0):
 print("(somePoint) is on the x-axis")
case (0, _):
 print("(somePoint) is on the y-axis")
case (-2...2, -2...2):
 print("(somePoint) is inside the box")
default:
 print("(somePoint) is outside of the box")
}

As mentioned above, we can use underline _ to ignore the value when matching:


switch tuples {
case (404, _):
 print("The http code is 404 not found.")
default: break
}

Use the where clause in switch case

Using the where clause in case makes our pattern matching look more compact, making matching patterns more compact:


let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
 print("((x), (y)) is on the line x == y")
case let (x, y) where x == -y:
 print("((x), (y)) is on the line x == -y")
case let (x, y):
 print("((x), (y)) is just some arbitrary point")
}

conclusion

The types of pattern matching in Swift

Pattern matching is a very powerful programming pattern in Swift. Good pattern matching can help us write brief and elegant code. Pattern matching in Swift includes the following types:

Conditional judgment: if, guard Optional binding: if let, guard let, while let... Circulation body: for, while, repeat while switch do catch

When is where clause used?

As we can see from the previous examples, where clauses are also used in many pattern matching. where clauses act as conditionals on the basis of pattern matching. Using where clauses is equivalent to:


for notNilValue in values {
 if notNilValue != nil {
  print("Not nil value: (String(describing: notNilValue!))")
 }
}

As you can see, using where clauses makes our code cleaner and easier to read. When should we use where? Or where can I use where? The detailed use of where is not covered in the Swift documentation, but it has been found in practice that where can be used in the following ways:

for loop statement switch branch

For if, guard, and while, we cannot add where clauses after them, because they can be combined with many conditions. Another use of where clauses is to impose type constraints on generic types, as described in the section on generics.


Related articles: