Rewriting Java in Scala & Making Code Lovely 6 - Pattern Matching

Pattern Matching - The Problem

I'm going to start off with a code snippet:

if (x instanceof SomeObject) {
  ((SomeObject)x).someMethod();
} else if (x instanceof SomeOtherObject) {
  ((SomeOtherObject)x).someOtherMethod();
}
Eugh. This is ugly. Not only that, it's something we all see quite often when writing Java. No matter how well we've designed our APIs to avoid needing this sort of thing, it always creeps in somewhere.

Scala gives us a (huge) feature called Pattern Matching that allows us to tidy this up:

x match {
  case y : SomeObject => y.someMethod()
  case y : SomeOtherObject => y.someOtherMethod()
}

This is very similar to Java's switch. The first case clause can be thought of as "if x is of type SomeObject then create a new variable, y, and set it to x". Unlike some languages, Scala does not have fall through, if the first clause is matched then no other clauses are checked.

I prefer this to Java's style of doing things because the cast/instance checking is done only once in your code. This is much more readable. Readability is only the tip of the iceberg. Pattern matching is very powerful and I'm now going to cover a few more uses of pattern matching.

Matching Constants

We can match against constants:

x match {
  case 1 => println("ONE!")
  case 2 => println("Two...")
  case _ => println("I can't count higher :(")
}

The first two cases here should be simple to follow. The third one is the same as Java's default when using switch.

We can also return values out of a pattern match:

y = x match {
  case "one" => 1
  case "two" => 2
  case _ => 3
}
Oh, we can match against strings too... or, if we really want, we can match both in the same match:
x match {
  case 1 => 1
  case "one" => 1
  case _ => 2
}

Matching Regular Expressions

Scala also allows us to match against regular expressions.

val timeformat = """(\d\d):(\d\d)""".r
val time = "19:21"
time match {
  case timeformat(h, m) => 
    println("It is " + m + " minutes past hour " + h)
  case _ => println("You gave me junk :(")
}

Read that again.

In the above example, we create a regular expression that matches times that look like "15:49", i.e. the standard western way of doing 24 hour times. We then pattern match on the string "19:21". The first clause will:

  • Check if the given string matches the regular expression
  • If a match is found, the two groups in the regular expression are substituted into variables h and m respectively

You can also ignore parts of the match using placeholder syntax (the underscore) if you want:

val timeformat = """(dd):(dd)""".r
val time = "4:21"
time match {
  case timeformat(h, _) => println("It is " + h + " o'clock")
  case _ => println("You gave me junk :(")
}

Short and readable. Nice.

Matching Types

Matching types can make equals method remarkably tidy. Suppose we're writing the equals method for ThisClass which has a single field called someField:

def equals(other : Any) =
  other match {
    case other : ThisClass => other.someField == this.someField
    case _ => false
  }
Easy. The first clause says that if other is of type ThisClass then cast it to ThisClass and put it in a new variable called other (we can re-use the same name here without a conflict). We can then use other to compare the fields. The second clause is the result if no match was found in the first clause (i.e. if other is not an instance of ThisClass).

Matching Anything

In Scala you can use something called an extractor object to match against anything you could ever want. I'm not going to go into this in this post as it's a big topic that deserves its own posting.

Summary

Many people compare pattern matching to "switch on steroids" or similar. This isn't an exaggeration. It's an extremely powerful tool and can lead to very succinct code (especially when doing things with regular expressions).

Discussion

blog comments powered by Disqus

Colin Howe

I'm Colin. I like coding, ultimate frisbee and startups. I am VP of engineering at Conversocial