pattern matching is common in functional language. case class is provided by Scala to allow pattern matching on objects without requiring a large amount of boilerplate.

Case Class

Classes with case modifier are called case classes. When a class is declared as a case class, following will be done by compiler:

  • a factory method with the name of the class is added to that class
  • all the arguments in the parameter list of the case class implicitly get a val prefix.
  • the compiler adds “natural” implementations of methods toString, hashCode, equals to the class. == will compare case classes structurally.
  • the compiler adds a copy method to the class for making modified copies

Pattern Matching

1
selector match { alternatives }

A pattern match includes a sequence of alternatives, each starting with the keyword case. A match expression is evaluated by trying each of the patterns in the order they are written. The first pattern that matches is selected, it will be executed but not fall through following patterns which is not like that in Java, where a keyword break is a must for the same effect.

  • A constant pattern matches values that are equal to the constant with respect to ==.
  • A variable pattern matches every value. The variable then refers to that value in the right hand side of the case clause.
  • The wildcard pattern _ also matches every value, but it does not introduce a variable name to refer to that value.
  • A constructor pattern matches certain case class with arguments also patterns. This makes deep patterns available with concise notations.
  • The sequence pattern like List or Array can be matched like case classes.
  • You can match against tuple patterns.
  • The typed patterns is the convenient replacement for type tests and type casts.

If no case pattern is matched, the match expression would throw a MatchError.

Scala uses a simple lexical rule for disambiguation between constant pattern and variable pattern: a simple name starting with a lowercase letter is take to be a pattern variable; all other references are taken to be constants. If the constant is a field of some object with a lowercase name, prefix it with a qualifier as this.pi. Or you can enclose the variable name in back ticks as pi.

If you want to match against a sequence without specifying its length, using _* as the last element of the pattern, this matches any number of elements within a sequence including zero elements.

Because of type erasure, there is no information about type arguments of generics maintained at runtime. The only exception to the erasure rule is arrays. Maybe reflection make type information available.

In addition to the standalone variable patterns, you can also add a variable to any other pattern. You simply write the variable name, an at sign @, and then the pattern. This gives you a variable-binding pattern.

The if statement following the pattern composes the pattern guard.

Sealed Classes

Making the superclass of the case classes sealed restricts adding new subclasses in the same file. This enables the compiler to detect missing combinations of patterns in the match expression.

Using @unchecked annotation on the selector expression of the match can make the compiler not emit warning on checking missing match expressions.

The Option type

You can also use pattern matching with Option values, which is Some(x) where x is the actual value or None object.

Case sequences as partial functions

A sequence of cases (i.e., alternatives) in curly braces can be used anywhere a function literal can be used. Essentially, a case sequence is a function literal, only more general. Instead of having a single entry point and list of parameters, a case sequence has multiple entry points, each with their own list of parameters. Each case is an entry point to the function, and the parameters are specified with the pattern. The body of each entry point is the right-hand side of the case.

One other generalization is worth noting: a sequence of cases gives you a partial function. If you apply such a function on a value it does not support, it will generate a run-time exception. For example:

1
2
3
val second: List[Int] => Int = {
  case x :: y :: _ => y
}

When compiling above code, the compiler will complain that the match is not exhaustive:

<console>:17: warning: match is not exhaustive!
missing combination    Nil

The type List[Int] => Int includes all functions from lists of integers to integers, whether or not the functions are partial. The type that only includes partial functions from lists of integers to integers is written PartialFunction[List[Int],Int] .

1
2
3
val second: PartialFunctin[List[Int], Int] = {
  case x :: y :: _ => y
}

Partial functions have a method isDefinedAt , which can be used to test whether the function is defined at a particular value. In this case, the function is defined for any list that has at least two elements.

Such an expression gets translated by the Scala compiler to a partial function by translating the patterns twice—once for the implementation of the real function, and once to test whether the function is defined or not. For instance, the function literal { case x :: y :: _ => y } above gets translated to the following partial function value:

1
2
3
4
5
6
7
8
9
10
new PartialFunction[List[Int], Int] {
  def apply(xs: List[Int]) = xs match {
    case x :: y :: _ => y
  }
  
  def isDefinedAt(xs: List[Int]) = xs match {
    case x :: y :: _ => true
    case _ => false
  }
}

This translation takes effect whenever the declared type of a function literal is PartialFunction . If the declared type is just Function1 , or is missing, the function literal is instead translated to a complete function.

In general, you should try to work with complete functions whenever possible, because using partial functions allows for runtime errors that the compiler cannot help you with. Sometimes partial functions are really helpful, though. You might be sure that an unhandled value will never be supplied. Alternatively, you might be using a framework that expects partial functions and so will always check isDefinedAt before calling the function.