Pattern Matching - rohgar/scala-principles-1 GitHub Wiki

The task we are trying to solve is find a general and convenient way to access objects in a extensible class hierarchy.

Attempts seen previously:

  • Classification and access methods: impractical
  • Type tests and casts: unsafe, low-level
  • Object-oriented decomposition: does not always work, need to touch all classes to add a new method.

Solution 2: Functional Decomposition with Pattern Matching

Observation: the sole purpose of test and accessor functions is to reverse the construction process:

  • Which subclass was used?
  • What were the arguments of the constructor? This situation is so common that many functional languages, Scala included, automate it.

Case Classes:

A case class definition is similar to a normal class definition, except that it is preceded by the modifier case.

trait Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr

Like before, this defines a trait Expr, and two concrete subclasses Number and Sum.

It also implicitly defines companion objects with apply methods:

//IMPLICIT:
object Number {
    def apply(n: Int) = new Number(n)
}

object Sum {
    def apply(e1: Expr, e2: Expr) = new Sum(e1, e2)
}

So you can write Number(1) instead of new Number(1). However, these classes are now empty. So how can we access the members?

Pattern Matching

Pattern matching is a generalization of switch from C/Java to class hierarchies. It’s expressed in Scala using the keyword match:

def eval(e: Expr): Int = e match {
    case Number(n) => n
    case Sum(e1, e2) => eval(e1) + eval(e2)
}

// So 
eval(4)   // will match case Number
eval(4+3) // will match case Sum

Rules:

  • match is followed by a sequence of cases, pat => expr.
  • Each case associates an expression expr with a pattern pat.
  • A MatchError exception is thrown if no pattern matches the value of the selector.

Forms of Patterns

Patterns are constructed from:

  • constructors, e.g. Number, Sum,
  • variables, e.g. n, e1, e2,
  • wildcard patterns _,
  • constants, e.g. 1, true.

More rules:

  • Variables always begin with a lowercase letter.
  • The same variable name can only appear once in a pattern. So, Sum(x, x) is not a legal pattern.
  • Names of constants begin with a capital letter, with the exception of the reserved words null, true, false.

How are Match Expression Evaluated:

An expression of the form

e match { case p1 => e1 ... case pn => en }

matches the value of the selector e with the patterns p1, ..., pn in the order in which they are written. Once the first matching case is found, the whole match expression is rewritten to the right-hand side of where the pattern matches the selector e.

Pattern Matching and Methods

Of course, it’s also possible to define the evaluation function as a method of the base classes or traits in the hierarchy. Example

trait Expr {
    def eval: Int = this match {
        case Number(n) => n
        case Sum(e1, e2) => e1.eval + e2.eval
    }
}

Bottomline: So here the limitations mentioned at the top of the page are resolved.