Pattern Matching

Scala has a built-in general pattern matching mechanism. It allows to match on any sort of data with a first-match policy. Here is a small example which shows how to match against an integer value:

object MatchTest1 extends App {
  def matchTest(x: Int): String = x match {
    case 1 => "one"
    case 2 => "two"
    case _ => "many" // case _ will trigger if all other cases fail.
  }
  println(matchTest(3)) // prints "many"
}

The block with the case statements defines a function which maps integers to strings. The match keyword provides a convenient way of applying a function (like the pattern matching function above) to an object.

Scala's pattern matching statement is most useful for matching on algebraic types expressed via case classes. Scala also allows the definition of patterns independently of case classes, using unapply methods in extractor objects.

Pattern matching returns something:

val stuff = "blue"

val myStuff = stuff match {
  case "red" =>
    println("RED"); 1
  case "blue" =>
    println("BLUE"); 2
  case "green" =>
    println("GREEN"); 3
  case _ =>
    println(stuff); 0 // case _ will trigger if all other cases fail.
}

myStuff should be(res0)

Pattern matching can return complex values:

val stuff = "blue"

val myStuff = stuff match {
  case "red" => (255, 0, 0)
  case "green" => (0, 255, 0)
  case "blue" => (0, 0, 255)
  case _ => println(stuff); 0
}

myStuff should be((res0, res1, res2))

Pattern matching can match complex expressions:

def goldilocks(expr: Any) =
  expr match {
    case ("porridge", "Papa") => "Papa eating porridge"
    case ("porridge", "Mama") => "Mama eating porridge"
    case ("porridge", "Baby") => "Baby eating porridge"
    case _ => "what?"
  }

goldilocks(("porridge", "Mama")) should be(res0)

Pattern matching can wildcard parts of expressions:

def goldilocks(expr: Any) =
  expr match {
    case ("porridge", _) => "eating"
    case ("chair", "Mama") => "sitting"
    case ("bed", "Baby") => "sleeping"
    case _ => "what?"
  }

goldilocks(("porridge", "Papa")) should be(res0)
goldilocks(("chair", "Mama")) should be(res1)

Pattern matching can substitute parts of expressions:

def goldilocks(expr: (String, String)) =
  expr match {
    case ("porridge", bear) =>
      bear + " said someone's been eating my porridge"
    case ("chair", bear) => bear + " said someone's been sitting in my chair"
    case ("bed", bear) => bear + " said someone's been sleeping in my bed"
    case _ => "what?"
  }

goldilocks(("porridge", "Papa")) should be(res0)
goldilocks(("chair", "Mama")) should be(res1)

A backquote can be used to refer to a stable variable in scope to create a case statement - this prevents "variable shadowing":

val foodItem = "porridge"

def goldilocks(expr: (String, String)) =
  expr match {
    case (`foodItem`, _) => "eating"
    case ("chair", "Mama") => "sitting"
    case ("bed", "Baby") => "sleeping"
    case _ => "what?"
  }

goldilocks(("porridge", "Papa")) should be(res0)
goldilocks(("chair", "Mama")) should be(res1)
goldilocks(("porridge", "Cousin")) should be(res2)
goldilocks(("beer", "Cousin")) should be(res3)

A backquote can be used to refer to a method parameter as a stable variable to create a case statement:

def patternEquals(i: Int, j: Int) =
  j match {
    case `i` => true
    case _ => false
  }
patternEquals(3, 3) should be(res0)
patternEquals(7, 9) should be(res1)
patternEquals(9, 9) should be(res2)

To pattern match against a List, the list can be split into parts, in this case the head x and the tail xs. Since the case doesn't terminate in Nil, xs is interpreted as the rest of the list:

val secondElement = List(1, 2, 3) match {
  case x :: xs => xs.head
  case _ => 0
}

secondElement should be(res0)

To obtain the second element you can expand on the pattern. Where x is the first element, y is the second element, and xs is the rest:

val secondElement = List(1, 2, 3) match {
  case x :: y :: xs => y
  case _ => 0
}

secondElement should be(res0)

Same koan as above, but we are pattern matching a list with only one item!

val secondElement = List(1) match {
  case x :: y :: xs => y // only matches a list with two or more items
  case _ => 0
}

secondElement should be(res0)

To pattern match against List, you can also establish a pattern match if you know the exact number of elements in a List:

val r = List(1, 2, 3) match {
  case x :: y :: Nil => y // only matches a list with exactly two items
  case _ => 0
}

r should be(res0)

If a pattern is exactly one element longer than a List, it extracts the final Nil:

val r = List(1, 2, 3) match {
  case x :: y :: z :: tail => tail
  case _ => 0
}

r == Nil should be(res0)