Scala best practice: Use match expressions and pattern matching

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 20.4, “Scala best practice: Use match expressions and pattern matching.”

Scala Problem

Match expressions (and pattern matching) are a major feature of the Scala programming language, and you want to see examples of the many ways to use them.

Scala Solution

Match expressions (match/case statements) and pattern matching are a major feature of the Scala language. If you’re coming to Scala from Java, the most obvious uses are:

  • As a replacement for the Java switch statement
  • To replace unwieldy if/then statements

However, pattern matching is so common, you’ll find that match expressions are used in many more situations:

  • In try/catch expressions
  • As the body of a function or method
  • With the Option/Some/None coding pattern
  • In the receive method of actors

The following examples demonstrate these techniques.

Replacement for the Java switch statement and unwieldy if/then statements

Recipe 3.8 showed that a match expression can be used like a Java switch statement:

val month = i match {
    case 1  => "January"
    case 2  => "February"
    // more months here ...
    case 11 => "November"
    case 12 => "December"
    case _  => "Invalid month"  // the default, catch-all
}

It can be used in the same way to replace unwieldy if/then/else statements:

i match {
    case 1 | 3 | 5 | 7 | 9 => println("odd")
    case 2 | 4 | 6 | 8 | 10 => println("even")
}

These are simple uses of match expressions, but they’re a good start.

In try/catch expressions

It helps to become comfortable with match expressions, because you’ll use them with Scala’s try/catch syntax. The following example shows how to write a try/catch expression that returns an Option when lines are successfully read from a file, and None if an exception is thrown during the file-reading process:

def readTextFile(filename: String): Option[List[String]] = {
    try {
        Some(Source.fromFile(filename).getLines.toList)
    } catch {
        case e: Exception => None
    }
}

To catch multiple exceptions in a try/catch expression, list the exception types in the catch clause, just like a match expression:

def readTextFile(filename: String): Option[List[String]] = {
    try {
        Some(Source.fromFile(filename).getLines.toList)
    } catch {
        case ioe: IOException =>
             logger.error(ioe)
             None
        case fnf: FileNotFoundException =>
             logger.error(fnf)
             None
    }
}

Note that if the specific error is important in a situation like this, use the Try/Success/Failure approach to return the error information to the caller, instead of Option/Some/None. See Recipe 20.6 for both Option and Try examples.

As the body of a function or method

As you get comfortable with match expressions, you’ll use them as the body of your methods, such as this method that determines whether the value it’s given is true, using the Perl definition of “true”:

def isTrue(a: Any) = a match {
    case 0 | "" => false
    case _ => true
}

In general, a match expression used as the body of a function will accept a parameter as input, match against that parameter, and then return a value:

def getClassAsString(x: Any):String = x match {
    case s: String => "String"
    case i: Int => "Int"
    case l: List[_] => "List"
    case p: Person => "Person"
    case Dog() => "That was a Dog"
    case Parrot(name) => s"That was a Parrot, name = $name"
    case _ => "Unknown"
}

As shown in Recipe 9.8, a match expression can also be used to create a partial function (i.e., working only for a subset of possible inputs):

val divide: PartialFunction[Int, Int] = {
    case d: Int if d != 0 => 42 / d
}

See that recipe for more details on this approach.

Use with Option/Some/None

Match expressions work well with the Scala Option/Some/None types. For instance, given a method that returns an Option:

def toInt(s: String): Option[Int] = {
    try {
        Some(s.toInt)
    } catch {
        case e: Exception => None
    }
}

You can handle the result from toInt with a match expression:

toInt(aString) match {
    case Some(i) => println(i)
    case None => println("Error: Could not convert String to Int.")
}

In a similar way, match expressions are a popular way of handling form verifications with the Play Framework:

verifying("If age is given, it must be greater than zero",
    model =>
        model.age match {
            case Some(age) => age < 0
            case None => true
        }
)

In actors

Match expressions are baked into actors as the way to handle incoming messages:

class SarahsBrain extends Actor {
    def receive = {
        case StartMessage => handleStartMessage
        case StopMessage => handleStopMessage
        case SetMaxWaitTime(time) => helper ! SetMaxWaitTime(time)
        case SetPhrasesToSpeak(phrases) => helper ! SetPhrasesToSpeak(phrases)
        case _ => log.info("Got something unexpected.")
    }
    // other code here ...
}

Summary

Match expressions are an integral part of the Scala language, and as shown, they can be used in many ways. The more you use them, the more uses you’ll find for them.

See Also

  • Match expressions are demonstrated in many examples in Chapter 3
  • Chapter 13 demonstrates the use of match expressions when writing actors