Figure

A blog about Swift and iOS development.

Custom Switch Matchers

We've made a lot of hay out of Swift's incredibly flexible switch statement on this blog. As we've seen, its pattern matching is particularly powerful when dealing with tuples:

switch (xCoordinate, yCoordinate){
case (0, 0):
  println("origin")
case let (0, y):
  println("No X, \(y) high.")
case let (x, 0):
  println("\(x) far, no Y.")
case (0...5, 5...10):
  println("In first quadrant")
case let (x, _):
  println("Who cares about Y? X is \(x)")
}

But we don't have to limit ourselves to tuples or simple value types when using a switch. With binding and the addition of a where clause, we can match any sort of arbitrary construct:

class Person{
  var name:String
  //...
}

switch myPersonObject{
case let x where x.name == "Gybrush":
  println("You fight like a cow.")
case let x where x.name == "Manny":
  println("One year later...")
case let x where x.name == "Vella":
  println("I'm going to kill Mog Chothra!")
default:
  println("I don't know who that is.")
}

Which is wonderful! At the same time… well, It's hard to ignore the ton of repetition going on. In each statement we're binding a value to a name, fetching a property from it, and finally comparing the property to a string. This final comparison is all that really matters; the rest is just boilerplate.

Wouldn't it be great if we could teach switch new matchers that knew how to deal with our custom objects? Believe it or not, there's an operator for that.

It's called the pattern match operator, sometimes known by its (less googleable) syntax: ~=. And switch statements use it behind the scenes to compare values to their cases' matching patterns.

All of which is to say, these two examples are (at least conceptually) identical:

switch myint{
case 0...10:
  println("first half")
case 11...20:
  println("second half")
default:
  println("out of range")
}

if 0...10 ~= myint{
  println("first half")
} else if 11...20 ~= myint{
  println("second half")
} else{
  println("out of range")
}

We can reasonably argue all its various merits and trade-offs, but this is a pretty clear-cut case of how operator overloading can open up a whole new world of expressiveness to us. For we can simply overload ~= like so:

func ~=(pattern:String, value:Person)->Bool{
  return pattern == value.name
}

And suddenly it's possible to refactor our switch statement down to:

switch myPersonObject{
case "Gybrush":
  println("You fight like a cow.")
case "Manny":
  println("One year later...")
case "Vella":
  println("I'm going to kill Mog Chothra!")
default:
  println("I don't know who that is.")
}

This is yet another example of Swift employing polymorphism in an unexpected way to make the type system do work for us. Work that results in very clean, concise, and — dare I say it — declarative code.


Hit me up on twitter (@jemmons) to continue the conversation.

Latest | Archives