Playing with Scala’s pattern matching
Table of Contents
Traditional approach
There is an approach to the Scala’s pattern matching that looks similar to the switch-case structure in C and Java: each case entry use an integer or any scalar type. Here is an example:
def toYesOrNo(choice: Int): String = choice match { case 1 => "yes"; case 0 => "no"; case _ => "error"; }
So if you enter toYesOrNo(1), Scala says “yes”. And if you enter toYesOrNo(2), Scala says “error”. Notice that the symbol _ is used to formulate the default case.You also have to remark that there is no need to use the break statement.
But if you want that Scala says “yes” when you enter toYesOrNo(1), toYesOrNo(2), or toYesOrNo(3), you will write the function like this:
def toYesOrNo(choice: Int): String = choice match { case 1 | 2 | 3 => "yes"; case 0 => "no"; case _ => "error"; }
So if you enter toYesOrNo(1), Scala says “yes”. And if you enter toYesOrNo(2), Scala says “error”. Notice that the symbol _ is used to formulate the default case.You also have to remark that there is no need to use the break statement. But if you want that Scala says “yes” when you enter toYesOrNo(1), toYesOrNo(2), or toYesOrNo(3), you will write the function like this:
def toYesOrNo(choice: Int): String = choice match { case 1 | 2 | 3 => "yes"; case 0 => "no"; case _ => "error" }
def parseArgument(arg: String) = arg match { case "-h" | "--help" => "displayHelp"; case "-v" | "--version" => "displayVerion"; case whatever => "unknownArgument"; }
So if you enter parseArgument(“-h”) or parseArgument(“–help”), Scala calls the displayHelp function. And if you enter parseArgument(“huh?”), Scala calls unknownArgument(“huh?”).
Notice that I have not used _ for the default case. Instead, I have put an identifier as its value is used in the associated instruction.
Typed pattern
def f(x: Any): String = x match { case i:Int => "integer: " + i case _:Double => "a double" case s:String => "I want to say " + s }
- f(1) → “integer: 1”
- f(1.0) → “a double”
- f(“hello”) → “I want to say hello”
Is not it better than a succession of if+instanceof+cast?This kind of pattern matching is useful to walk through a structure using the composite design pattern. For example, you can use it to explore the DOM of an XML document or the object model of a JSON message.
Functional approach to pattern matching
def fact(n: Int): Int = if (n == 0) 1 else n * fact(n - 1)
def fact(n: Int): Int = n match { case 0 => 1 case n => n * fact(n - 1) }
Notice the use of the variable n in this case. It is matched with any value that does not appears in the preceding cases. You have to understand that the n in the last case is not the same as the one used in the function signature. If you wanted to use the parameter n and not a new variable with the same name, you have to delimit the variable name with back-quote in the case definition, like this: `n`
Here is a simple way to build you factorial function in Scala:
def fact(n) = (1 to n).foldLeft(1) { _ * _ }
Pattern matching and collection: the look-alike approach
Pattern matching may be applied to the collections. Below is a function that computes the length of a list without pattern matching:
def length[A](list : List[A]) : Int = { if (list.isEmpty) 0 else 1 + length(list.tail) }
Here is the same function with pattern matching:
def length[A](list : List[A]) : Int = list match { case _ :: tail => 1 + length(tail) case Nil => 0 }
n this function, there are two cases. The last one checks if the list is empty with the Nil value. The first one checks if there is at least one element is the list. The notation _ :: tail
should be understood “a list with whatever head followed by a tail”. Here the tail can be Nil (ie. empty list) or a non-empty list.
To go further, we will use this approach with tuples in order to improve our method parserArgument() above:
def parseArgument(arg : String, value: Any) = (arg, value) match { case ("-l", lang) => setLanguageTo(lang) case ("-o" | "--optim", n : Int) if ((0 < n) && (n <= 5)) => setOptimizationLevelTo(n) case ("-o" | "--optim", badLevel) => badOptimizationLevel(badLevel) case ("-h" | "--help", null) =>displayHelp() case bad => badArgument(bad) }
Notice first the use of the operator | that allows to match alternative forms of arg inside the tuple. Notice also the use of two patterns for options -o
and --optim
. These patterns are distinguished by the use of a guard condition. The guard conditions help you to get fine tuned pattern matching when pattern matching alone is not enough.