Chapter 5. Pattern Matching

So far, we've explored some of the basic functional cornerstones of Scala: immutable data types and the passing of functions as parameters. The third cornerstone of functional programming is pattern matching. At first glance, pattern matching looks like Java's switch statement. However, pattern matching provides a powerful tool for declaring business logic in a concise and maintainable way. Scala blends traditional functional programming pattern matching with object-oriented concepts to provide a very powerful mechanism for writing programs.

In this chapter, we're going to explore the basics of pattern matching. Then we're going to see how Scala's case classes bridge between object-oriented data encapsulation and function decomposition. Next, we'll see how Scala's pattern-matching constructs become functions that can be passed around and composed. Finally, we'll see how pattern matching provides a flexible alternative to the visitor pattern.

Basic Pattern Matching

Pattern matching, at its core, is a very complex set of if/else expressions and looks a lot like Java's switch statement. Let's start with a very simple example: calculating Fibonacci numbers:

def fibonacci(in: Int): Int = in match {
    case 0 => 0
    case 1 => 1
    case n => fibonacci(n − 1) + fibonacci(n − 2)
  }

Let's write the same code in Java:

public int fibonacci(int in) {
    switch (in) {
      case 0:
        return 0;
case 1:
        return 1;
      default:
        return fibonacci(in − 1) + fibonacci(in − 2);
    }
  }

There is little difference between the Scala and Java versions. Note that there's no break statement between cases in Scala, where you need break or return at the end of the case in Java. Note also that the last case in Scala assigns the default value to the variable n. Pattern matching in Scala is also an expression that returns a value.

In Scala, we can have multiple tests on a single line:

case 0 | −1 | −2 => 0

That code corresponds to the following in Java:

case 0:
case −1:
case −2:
  return 0;

However, Scala allows guards to be placed in patterns to test for particular conditions that cannot be tested in the pattern declaration itself. Thus, we can write our Fibonacci calculator to return 0 if a negative number is passed in:

def fib2(in: Int): Int = in match {
    case n if n <= 0 => 0
    case 1 => 1
    case n => fib2(n − 1) + fib2(n − 2)
  }

case n if n <= 0 => 0 is the first test in the pattern. The test extracts the value into the variable n and tests n to see whether it's zero or negative and returns 0 in that case. Guards are very helpful as the amount of logic gets more complex. Note that the case statements are evaluated in the order that they appear in the code. Thus, case n if n <= 0 => is tested before case n =>. Under the hood, the compiler may optimize the pattern[16] and minimize the number of tests, cache test results, and even cache guard results.

Matching Any Type

Like C#, Scala can pattern match against any type. Let's see how Scala pattern matching works with Strings:

def myMules(name: String) = name match {
    case "Elwood" | "Madeline" => Some("Cat")
    case "Archer" => Some("Dog")
    case "Pumpkin" | "Firetruck" => Some("Fish")
    case _ => None
  }

The corresponding code in Java looks like the following:

public String myMules(String name) {
    if (name.equals("Elwood") || name.equals("Madline")) {
      return "Cat";
    } else if (name.equals("Archer")) {
      return "Dog";
    } else if (name.equals("Pumpkin") || name.equals("Firetruck")) {
      return "Fish";
    } else {
      return null;
    }
  }

If you're curious about how the Scala compiler expands a pattern into code, you can use the -print option in the Scala compiler. Create the MyMules.scala program:

object MyMules {
  def myMules(name: String) = name match {
    case "Elwood" | "Madeline" => Some("Cat")
    case "Archer" => Some("Dog")
    case "Pumpkin" | "Firetruck" => Some("Fish")
    case _ => None
  }
}

Compile it with the following line:

scalac -print MyMules.scala

The result follows:

package <empty> {
  final class MyMules extends java.lang.Object with ScalaObject {
    @remote def $tag(): Int = scala.ScalaObject$class.$tag(MyMules.this);
    def myMules(name: java.lang.String): Option = {
      <synthetic> val temp1: java.lang.String = name;
      if (temp1.==("Elwood"))
        body%0(){
          new Some("Cat")
        }
      else
        if (temp1.==("Madeline"))
          body%0()
        else
          if (temp1.==("Archer"))
            body%1(){
              new Some("Dog")
            }
          else
            if (temp1.==("Pumpkin"))
              body%2(){
                new Some("Fish")
              }
            else
              if (temp1.==("Firetruck"))
                body%2()
              else
                body%3(){
                  scala.None
                }
    };
    def this(): object MyMules = {
      MyMules.super.this();
      ()
    }
  }
}

More Pattern Matching

Patterns can match across different types in the same statement:

def test1(in: Any): String = in match {
    case 1 => "One"
    case "David" | "Archer" | Some("Dog") => "Walk"
    case _ => "No Clue"
  }

The previous code introduces the _ as a wildcard pattern. This is consistent with Scala's use of the underscore as a wildcard in other contexts.

Testing Data Types

Pattern matching is a very powerful way to avoid explicit casting. In Java, there is a separation between the instanceof test and the casting operation. This often results in bugs when a block of test/cast code is copied and pasted. There's no compiler check that the instanceof test matches the cast, and it's not uncommon to have a mismatch between the test and the cast in Java code that's been copied and pasted. Let's write a method that tests an incoming Object to see whether it's a String, an Integer, or something else. Depending on what type it is, different actions will be performed.

public String test2(Object in) {
    if (in == null) {
      return "null";
    }
    if (in instanceof String) {
      String s = (String) in;
      return "String, length " + s.length();
    }
    if (in instanceof Integer) {
      int i = ((Integer) in).intValue();
      if (i > 0) {
        return "Natural Int";
      }
      return "Another Int";
    }

    return in.getClass().getName();
  }

The same code in Scala is shorter, and there's no explicit casting.

def test2(in: Any) = in match {
    case s: String => "String, length "+s.length
    case i: Int if i > 0 => "Natural Int"
    case i: Int => "Another Int"
    case a: AnyRef => a.getClass.getName
    case _ => "null"
  }

The first line tests for a String. If it is a String, the parameter is cast into a String and assigned to the s variable, and the expression on the right of the => is returned. Note that if the parameter is null, it will not match any pattern that compares to a type. On the next line, the parameter is tested as an Int. If it is an Int, the parameter is cast to an Int, assigned to i, and the guard is tested. If the Int is a natural number (greater than zero), "Natural Int" will be returned. In this way, Scala pattern matching replaces Java's test/cast paradigm. I find that it's very, very rare that I do explicit testing and casting in Scala.

Case Classes

We saw case classes earlier in the book. They are classes that get toString, hashCode, and equals methods automatically. It turns out that they also get properties and extractors. Case classes also have properties and can be constructed without using new.

Let's define a case class:

case class Person(name: String, age: Int, valid: Boolean)

Let's create an instance of one:

scala> val p = Person("David", 45, true)
p: Person = Person(David,45,true)

You may use new to create a person as well:

scala> val m = new Person("Martin", 44, true)
m: Person = Person(Martin,44,true)

Each of the Person instances has properties that correspond to the constructor parameters:

scala> p.name
res0: String = David
scala> p.age
res1: Int = 45
scala> p.valid
res2: Boolean = true

By default, the properties are read-only, and the case class is immutable.

scala> p.name = "Fred"
<console>:7: error: reassignment to val
       p.name = "Fred"

You can also make properties mutable:

scala> case class MPerson(var name: String, var age: Int)
defined class MPerson
scala> val mp = MPerson("Jorge", 24)
mp: MPerson = MPerson(Jorge,24)
scala> mp.age = 25
scala> mp
res3: MPerson = MPerson(Jorge,25)

So far, this is just some syntactic sugar. How, you ask, does it work with pattern matching?

Pattern matching against case classes is syntactically pleasing and very powerful. We can match against our Person class, and we get the extractors for free:

def older(p: Person): Option[String] = p match {
  case Person(name, age, true) if age > 35 => Some(name)
  case _ => None
}

Our method matches against instances of Person. If the valid field is true, the age is extracted and compared against a guard. If the guard succeeds, the Person's name is returned, otherwise None is returned. Let's try it out:

scala> older(p)
res4: Option[String] = Some(David)
scala> older(Person("Fred", 73, false))
res5: Option[String] = None
scala> older(Person("Jorge", 24, true))
res6: Option[String] = None

Pattern Matching in Lists

As we saw in Chapter 3's Roman numeral example (Listing 3-5), Scala's pattern matching can also be applied to Lists. Scala's List collection is implemented as a linked list where the head of the list is called a cons cell.[17] It contains a reference to its contents and another reference to the tail of the list, which may be another cons cell or the Nil object. Lists are immutable, so the same tail can be shared by many different heads. In Scala, the cons cell is represented by the :: case class. Perhaps you have just said, "Ah hah!" Creating a List is Scala is as simple as this:

1 :: Nil

:: is the name of the method and the name of a case class. By keeping the creation method, ::, and the case class name the same, we can construct and pattern match Lists in a syntactically pleasing way. And as we've just seen, case classes can be used in pattern matching to either compare or extract values. This holds for Lists as well and leads to some very pleasing syntax.

We construct a List with

scala> val x = 1
x: Int = 1
scala> val rest = List(2,3,4)
rest: List[Int] = List(2, 3, 4)
scala> x :: rest
res1: List[Int] = List(1, 2, 3, 4)
scala> (x :: rest) match { // note the symmetry between creation and matching
       case xprime :: restprime => println(xprime); println(restprime)
       }
1
List(2, 3, 4)

Then we can extract the head (x) and tail (rest) of the List in pattern matching.

Pattern Matching and Lists

Pattern matching and Lists go hand in hand. We can start off using pattern matching to sum up all the odd Ints in a List[Int].

def sumOdd(in: List[Int]): Int = in match {
    case Nil => 0
    case x :: rest if x % 2 == 1 => x + sumOdd(rest)
    case _ :: rest => sumOdd(rest)
  }

If the list is empty, Nil, then we return 0. The next case extracts the first element from the list and tests it to see whether it's odd. If it is, we add it to the sum of the rest of the odd numbers in the list. The default case is to ignore the first element of the list (a match with the _ wildcard) and return the sum of the odd numbers in the rest of the list.

Extracting the head of a list is useful, but when pattern matching against List, we can match against any number of elements in the List. In this example, we will replace any number of contiguous identical items with just one instance of that item:

def noPairs[T](in: List[T]): List[T] = in match {
    case Nil => Nil
    case a :: b :: rest if a == b => noPairs(a :: rest)
      // the first two elements in the list are the same, so we'll
      // call noPairs with a List that excludes the duplicate element
    case a :: rest => a :: noPairs(rest)
      // return a List of the first element followed by noPairs
      // run on the rest of the List
  }

Let's run the code and see that it does what we expect:

scala> noPairs(List(1,2,3,3,3,4,1,1))
res6: List[Int] = List(1, 2, 3, 4, 1)

Pattern matching can match against constants as well as extract information. Say we have a List[String] and we want to implement a rule that says that we discard the element preceding the "ignore" String. In this case, we'll use pattern matching to test as well as extract:

def ignore(in: List[String]): List[String] = in match {
    case Nil => Nil
    case _ :: "ignore" :: rest => ignore(rest)
      // If the second element in the List is "ignore" then return the ignore
      // method run on the balance of the List
    case x :: rest => x :: ignore(rest)
      // return a List created with the first element of the List plus the
      // value of applying the ignore method to the rest of the List
  }

Let's compare this code to Java code that does the same thing. In the Scala code, the pattern matching takes care of length testing and other plumbing. Additionally, because the Scala code is recursive, there's no need for explicit looping or for setting up the accumulator. Looking at the Java code, there's a lot of boilerplate. The logic of incrementing the loop counter exists in different places, and the test (x < len − 1) is not intuitive. In fact, when I wrote the example, I got this test wrong; it wasn't until I ran the code that I discovered the problem.

public ArrayList<String> ignore(ArrayList<String> in) {
    ArrayList<String> ret = new ArrayList<String>();
    int len = in.size();
for (int x = 0; x < len;) {
      if (x < len - 1 && in.get(x + 1).equals("ignore")) {
        x += 2;
      } else {
        ret.add(in.get(x));
        x++;
      }
    }

   return ret;
  }

We've seen how to use pattern matching and Lists with extraction and equality testing. We can also use the class test/cast mechanism to find all the Strings in a List[Any]:

def getStrings(in: List[Any]): List[String] = in match {
    case Nil => Nil
    case (s: String) :: rest => s :: getStrings(rest)
    case _ :: rest => getStrings(rest)
  }

However, the paradigmatic way of filtering a List[Any] into a List of a particular type is by using a pattern as a function. We'll see this in the "Pattern Matching As Functions" section.

In this section, we've explored how to do pattern matching. We've seen extraction and pattern matching with Lists. It may seem that List is a special construct in Scala, but there's nothing special about List in Scala. Let's look a little more at case classes.

Nested Pattern Matching in Case Classes

Case classes can contain other case classes, and the pattern matching can be nested. Further, case classes can subclass other case classes. For example, let's create the MarriedPerson subclass of Person:

case class MarriedPerson(override val name: String,
  override val age: Int,
  override val valid: Boolean,
  spouse: Person) extends Person(name, age, valid)

We've defined the class. Note that the override val syntax is ugly. It's one of the ugliest bits in Scala.

And let's create a new instance of MarriedPerson:

scala> val sally = MarriedPerson("Sally", 24, true, p)
sally: MarriedPerson = MarriedPerson(Sally,24,true,Person(David,45,true))

Let's create a method that returns the name of someone who is older or has a spouse who is older:

def mOlder(p: Person): Option[String] = p match {
  case Person(name, age, true) if age > 35 => Some(name)
  case MarriedPerson(name, _, _, Person(_, age, true))
    if age > 35 => Some(name)
  case _ => None
}

Let's see the new method in action:

scala> mOlder(p)
res7: Option[String] = Some(David)
scala> mOlder(sally)
res8: Option[String] = Some(Sally)

Scala's case classes give you a lot of flexibility for pattern matching, extracting values, nesting patterns, and so on. You can express a lot of logic in pattern declarations. Further, patterns are easy for people to read and understand, which makes code maintenance easier. And because Scala is statically typed, the compiler will help detect some code problems.

Examining the Internals of Pattern Matching

The next couple of paragraphs get into some gnarly parts of Scala. Let's write our own class that is nearly as syntactically pleasing as Scala's List. We will rely on a couple of syntactic features in Scala. The first is that Scala methods and classes can have operator characters as class names. The following are all valid method and class names: Foo, Foo_?, foo32, ?, ?:. The second is that methods that have a colon as their last character are evaluated right to left, rather than left to right. That means 3 :: Nil is desugared to Nil.::(3). Using these two features of Scala, let's define our own MList class that has the same pattern-matching beauty as Scala's List class. The code in this section must be compiled because there are circular class references. You cannot type this code at the REPL. Here's the whole listing:

class MList[+T] {
  def ?:[B >: T](x: B): MList[B] = new ?:(x, this)
}

case object MNil extends MList[Nothing]

case class ?:[T](hd: T, tail: MList[T]) extends MList[T]

First, let's define MList. It has a type of +T, which is part of Scala's type system, and it means that subclasses of MList have a covariant type.[18] Covariant means that T in subclasses of MList can be the same class or a superclass of T.

class MList[+T] {
  def ?:[B >: T](x: B): MList[B] = new ?:(x, this)
}

MList contains a single method, ?:, which is also the class name of the linked list cons cell. This allows chaining of constructing lists. Also, note that B, which is the type of the parameter, relates to T using the >: type relationship operator. This means that B must be the same class or a superclass of T. So, if you've got an MList[String] and you add an Int cell, the MList's type becomes the class that's the superclass of both: Any. If you have an MList[Number] and you add an Int, the list is still MList[Number].

Next, let's define the MNil singleton. This is an MList[Nothing]. Nothing is the subclass of every other class.[19] Because MList is covariant, MList[Nothing] can serve as a member of every MList. If we've got MNil and we call the ?: method with a String, because the superclass of the two is String, we add a String cell to MNil resulting in an MList[String].

case object MNil extends MList[Nothing]

Finally, let's define our cons cell. The cons cell holds the node and links to the tail of the list. The class name of the cons cell is ?:, which is the method name on MList that adds a new cell at the head of the list. The case class name ?: is the same as the method name ?: to unify the syntax of creating and pattern matching against the MList.

case class ?:[T](hd: T, tail: MList[T]) extends MList[T]

Finally, let's see how our new MList class looks in pattern matching:

def tryMList(in: MList[Any]) = in match {
    case 1 ?: MNil => "foo"
    case 1 ?: _ => "bar"
    case _ => "baz"
  }

So, this demonstrates that there's no internal magic to support Scala's List class in pattern matching. You can write your own classes that are as syntactically pleasing as Scala's libraries.

Pattern Matching As Functions

Scala patterns are syntactic elements of the language when used with the match operator. However, you can also pass pattern matching as a parameter to other methods. Scala compiles a pattern match down to a PartialFunction[A,B], which is a subclass of Function1[A,B]. So a pattern can be passed to any method that takes a single parameter function. This allows us to reduce

list.filter(a => a match {
      case s: String => true
      case _ => false
  })

to

list.filter {
    case s: String => true
    case _ => false
  }

Because patterns are functions and functions are instances, patterns are instances. In addition to passing them as parameters, they can also be stored for later use.

In addition to Function1's apply method, PartialFunction has an isDefinedAt method so that you can test to see whether a pattern matches a given value. If you try to apply a PartialFunction that's not defined for the value, a MatchError will be raised. How is this useful?

If you're building a web application, you might have particular URLs that need special handling while others get handled in the default manner. The URL can be expressed as a List[String]. We can do the following:

def handleRequest(req: List[String])(
    exceptions: PartialFunction[List[String], String]): String =
    if (exceptions.isDefinedAt(req)) exceptions(req) else
    "Handling URL "+req+" in the normal way"

So, if the partial function exceptions (the pattern) matches the request req according to the isDefinedAt method, then we allow the request to be handled by the exceptions function. Otherwise, we do default handling. We can call handleRequest and handle any "api" requests by a separate handler:

handleRequest("foo" :: Nil) {
    case "api" :: call :: params => doApi(call, params)
  }

  def doApi(call: String, params: List[String]): String =
  "Doing API call "+call

Partial functions can be composed into a single function using the orElse method.[20] So, we can define a couple of partial functions:

val f1: PartialFunction[List[String], String] = {
    case "stuff" :: Nil => "Got some stuff"
  }

  val f2: PartialFunction[List[String], String] = {
    case "other" :: params => "Other: "+params
  }

And we can compose them:

val f3 = f1 orElse f2

And we can pass them into the handleRequest method:

handleRequest("a" :: "b" :: Nil)(f3)

In this way, Scala gives you a very nice, declarative way of handling complex filtering tasks. Partial functions can match on data and can be passed around like any other instances in Scala. Partial functions replace a lot of the XML configuration files in Java because pattern matching gives you the same declarative facilities as a configuration file, but they are type-safe, high-performance, and they can have guards and generally take advantage of any method in your code. Here's an example of using pattern matching to dispatch REST request in the ESME[21] code:[22]

def dispatch: LiftRules.DispatchPF = {
    case Req("api" :: "status" :: Nil, "", GetRequest) => status
    case Req("api" :: "messages" :: Nil, "", GetRequest) => getMsgs
    case Req("api" :: "messages" :: "long_poll" :: Nil, "", GetRequest) =>
      waitForMsgs
    case Req("api" :: "messages" :: Nil, "", PostRequest) =>
      () => sendMsg(User.currentUser.map(_.id.is), S)

  case Req("api" :: "follow" :: Nil, _, GetRequest) =>
      following(calcUser)
    case Req("api" :: "followers" :: Nil, _, GetRequest) =>
      followers(calcUser)
    case Req("api" :: "follow" :: Nil, _, PostRequest) =>
      performFollow(S.param("user"))
  }

Object-Oriented and Functional Tensions

At this point, the hard-core object-oriented designer folks may be somewhat unhappy about Scala case class's exposure of lots of internal information. Data hiding is an important part of OOP's abstraction. But in fact, most of the Java classes we define have getters and setters, so there is data exposed in OOP. But there is a tension between the amount of internal state that's exposed in our program and the amount of state that's hidden. In this section, we'll explore OOP and functional programming (FP) patterns for data hiding and exposure.

Another tension in OOP is how to define methods on class and interface hierarchies. Where does a method definition belong? What happens when a library is deployed but it's necessary to add new functionality to subclasses? How do we retrofit the defined-in-stone library classes to add this functionality? Put more concretely, if we have a library of shapes—circle, square, rectangle—that each have an area method but hide all their other data, how do we add a perimeter method to the shapes? Let's explore the tension and the tools Scala and FP give us to address the tension.

Shape Abstractions

If we have a collection of shapes that derive from the common trait OShape that has an area method on it, our object definitions would look something like the following if we used a traditional OOP approach:

trait OShape {
    def area: Double
  }

 class OCircle(radius: Double) extends OShape {
    def area = radius * radius * Math.Pi
  }
  class OSquare(length: Double) extends OShape {
    def area = length * length
  }
  class ORectangle(h: Double, w: Double) extends OShape {
    def area = h * w
  }

Let's compare this with the pattern-matching implementation:

trait Shape

  case class Circle(radius: Double) extends Shape
  case class Square(length: Double) extends Shape
  case class Rectangle(h: Double, w: Double) extends Shape

object Shape {
  def area(shape: Shape): Double = shape match {
    case Circle(r) => r * r * Math.Pi
    case Square(l) => l * l
    case Rectangle(h, w) => h * w
  }
}

In the pattern-matching example, all of the logic for calculating area is located in the same method, but the fact that the method exists is not obvious from looking at the Shape trait. So far, the OOP methodology seems to be the right answer because it makes obvious what shapes can do.

However, if we have a shape library and we want to calculate the perimeter of each of the shapes, there's a benefit to pattern matching:

def perimeter(shape: Shape) = shape match {
    case Circle(r) => 2 * Math.Pi * r
    case Square(l) => 4 * l
    case Rectangle(h, w) => h * 2 + w * 2
  }

In this case, the open data makes implementing the perimeter method possible. With the OOP implementation, we would have to expose data to make the perimeter method possible to implement. So our OOP implementation would look like

trait OShape {
    def area: Double
  }

  class OCircle(radius: Double) extends OShape {
    def area = radius * radius * Math.Pi
    def getRadius = radius
  }
  class OSquare(length: Double) extends OShape {
    def area = length * length
    def getLength = length
  }
  class ORectangle(h: Double, w: Double) extends OShape {
    def area = h * w
    def getHeight = h
    def getWidth = w
  }

More broadly, it's rare that the designer of an object hierarchy implements all the methods that a library consumer is going to need.

The visitor pattern is a design pattern that allows you to add functionality to a class hierarchy after the hierarchy is already defined. Let's look at a typical visitor pattern implementation. Following is the interface that defines the visitor. The code contains circular class references and will not work at the REPL. So, first the code, and then a walk-through of the code:

trait OCarVisitor {
  def visit(wheel: OWheel): Unit
  def visit(engine: OEngine): Unit
  def visit(body: OBody): Unit
  def visit(car: OCar): Unit
}

trait OCarElement {
  def accept(visitor: OCarVisitor): Unit
}

class OWheel(val name: String) extends OCarElement {
  def accept(visitor: OCarVisitor) = visitor.visit(this)
}

class OEngine extends OCarElement {
  def accept(visitor: OCarVisitor) = visitor.visit(this)
}

class OBody extends OCarElement {
  def accept(visitor: OCarVisitor) = visitor.visit(this)
}

class OCar extends OCarElement {
  val elements = List(new OEngine, new OBody, new OWheel("FR"),
                      new OWheel("FL"), new OWheel("RR"), new OWheel("RL"))

  def accept(visitor: OCarVisitor) =
  (this :: elements).foreach(_.accept(visitor))
}

The library author has to think about extensibility and implement the visitor pattern. Note also that the class hierarchy is fixed in the visitor because the visitor has to implement an interface that defines all the possible classes that the visitor can handle:

trait OCarVisitor {
    def visit(wheel: OWheel): Unit
    def visit(engine: OEngine): Unit
    def visit(body: OBody): Unit
    def visit(car: OCar): Unit
  }

Each element derives from a trait that creates a contract, which requires that the class implement the accept method:

trait OCarElement {
    def accept(visitor: OCarVisitor): Unit
  }

We implement each subclass and implement the accept method:

class OWheel(val name: String) extends OCarElement {
    def accept(visitor: OCarVisitor) = visitor.visit(this)
  }

  class OEngine extends OCarElement {
    def accept(visitor: OCarVisitor) = visitor.visit(this)
  }

  class OBody extends OCarElement {
    def accept(visitor: OCarVisitor) = visitor.visit(this)
  }

  class OCar extends OCarElement {
    val elements = List(new OEngine, new OBody, new OWheel("FR"),
                        new OWheel("FL"), new OWheel("RR"), new OWheel("RL"))

    def accept(visitor: OCarVisitor) =
    (this :: elements).foreach(_.accept(visitor))
  }

That's a lot of boilerplate.[23] Additionally, it violates the data-hiding principles of OOP because the visitor has to access some of the data in each element that it visits. Let's compare the pattern-matching version:

trait CarElement
  case class Wheel(name: String) extends CarElement
  case class Engine() extends CarElement
  case class Body() extends CarElement
  case class Car(elements: List[CarElement]) extends CarElement

The code is cleaner because there's no boilerplate accept method. Let's see what we do when we want to traverse the object hierarchy:

def doSomething(in: CarElement): Unit = in match {
    case Wheel(name) =>
    case Engine() =>
    case Body() =>
    case Car(e) => e.foreach(doSomething)
  }

More generally, Burak Emir, one of Scala's authors, wrote an excellent paper on the intersection of pattern matching and object-oriented design. See http://library.epfl.ch/theses/?nr=3899.

Summary

In this chapter, we explored pattern matching and saw how pattern matching provides powerful declarative syntax for expressing complex logic. Pattern matching provides an excellent and type-safe alternative to Java's test/cast paradigm. Pattern matching used with case classes and extraction provides a powerful way to traverse object hierarchies and is an excellent alternative to the visitor pattern. And because patterns are functions and objects, they can be passed as parameters and used wherever functions are used.

In the next chapter, we'll explore Actors. Actors provide a great paradigm for concurrency without locks. Actors in Scala are entirely library-based. They take advantage of Scala's flexible syntax, interoperability with Java libraries, and pattern matching to provide awesome power and flexibility for building multicore-friendly applications.



[16] A huge thanks to David MacIver for improving Scala's pattern-matching code.

[17] The naming of the cons cell traces its roots back to Lisp and came from the act of constructing a list. One constructs a list by linking a cons cell to the head of the list.

[18] Covariance and contravariance are scary terms and big concepts. We'll dive into them in Chapter 7.

[19] It works best if you say it out loud.

[20] Technically, combining multiple partial functions using orElse is not functional composition. A roomful of wicked smart functional developers at LShift (http://lshift.com) were unable to come up with a good name other than "functional smooshing."

[21] ESME is the Enterprise Social Messaging Experiment (http://blog.esme.us).

[22] This code will not compile without the rest of the ESME code, but it serves as an illustration of using pattern matching as an alternative to XML configuration files or annotations.

[23] Here is where a unityped language such as Ruby or Python has a material advantage over a static language such as Java. In Ruby, you don't need all the boilerplate, and the class hierarchy is not fixed at the time the OCarVisitor interface is defined.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.253.163