Advanced Pattern Matching

Expression Pattern

released Fri, 15 Feb 2019
Swift Version 5.0

Expression Pattern

The expression pattern is very powerful. It matches the switch value against an expression implementing the ~= operator. There're default implementations for this operator, for example for ranges, so that you can do:

switch 5 {

case 0..10: print(\"In range 0-10\")

default: print(\"In another range\")

}

However, the much more interesting possibility is overloading the operator yourself in order to add matchability to your custom types. Let's say that you decided to rewrite the soldier game we wrote earlier and you want to use structs after all.

struct Soldier {

   let hp: Int

   let x: Int

   let y: Int

}

Now you'd like to easily match against all entities with a health of 0. We can simply implement the ~= operators as follows.

func ~= (pattern: Int, value: Soldier) -> Bool {

     return pattern == value.hp

}

Now we can match against an entity. In this example, only soldiers that have a hp of 0 would be matched (thus, we print dead soldier), because we're commparing the value.hp to the switch pattern in our ~= implementation above.

let soldier = Soldier(hp: 99, x: 10, y: 10)

switch soldier {

case 0: print(\"dead soldier\")

default: ()

}

What if you'd like to not just compare the hp but also the x and the y? You can just implement pattern with a tuple:

func ~= (pattern: (hp: Int, x: Int, y: Int), value: Soldier) -> Bool {

     let (hp, x, y) = pattern

     return hp == value.hp && x == value.x && y == value.y

}





let soldier = Soldier(hp: 99, x: 10, y: 10)

switch soldier {

case (50, 10, 10): print(\"health 50 at pos 10/10\")

default: ()

}

You can even match structs against structs. However, this only works if your structs are Equatable. Swift can implement this automatically, as long as you tell it to by conforming to the protocol. So lets first extend our Soldier struct to conform to Equatable:

struct Soldier: Equatable {

     let hp: Int

     let x: Int

     let y: Int

}

Now, we can add a new match implementation. Since both soldiers are equatable value types, we can actually just directly compare them. If they both have the same values for their three properties (hp, x, y), then they are considered equal:

func ~= (pattern: Soldier, value: Soldier) -> Bool {

     return pattern == value

}



let soldier = Soldier(hp: 50, x: 10, y: 10)

switch soldier {

case Soldier(hp: 50, x: 10, y: 10): print(\"The same\")

default: ()

}

The left side of the ~= operator (the pattern argument) can be anything. So it can even be a protocol:

protocol Entity {

     var value: Int {get}

}



struct Tank: Entity {

     var value: Int

     init(_ value: Int) { self.value = value }

}



struct Peasant: Entity {

     var value: Int

     init(_ value: Int) { self.value = value }

}



func ~=(pattern: Entity, x: Entity) -> Bool {

     return pattern.value == x.value

}



switch Tank(42) {

case Peasant(42): print(\"Matched\") // Does match

default: ()

}

There's a lot of things you can do with Expression Patterns. For a much more detailed explanation of Expression Patterns, have a look at this terrific blog post by Austin Zheng.

This completes list of possible switch patterns. Our next topic is flow control in switch statements.