CHAPTER 5

image

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. 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. Let's look at the simple example first.

Basic Pattern Matching

Pattern matching allows you to make a programmatic choice between multiple conditions, such as, in the case boolean x is true, print a “true” message; in the case that x is false, print a “false” message. However, let this simple example not beguile you into underestimating the true power of Scala’s pattern matching. Scala’s pattern matching allows your cases to be far more complex than merely the case whether x is true or false. In Scala, your cases can include types, wildcards, sequences, regular expressions, and so forth. Let’s start with a simple example: printing numbers as shown in Listing 5-1.

Listing 5-1. Printing Numbers

def printNum(int: Int) {
 int match {
  case 0 => println("Zero")
  case 1 => println("One")
  case _ => println("more than one")
  }
}
scala> printNum(0)
Zero
scala> printNum(1)
One
scala> printNum(2)
more than one

As you can see, there are no big ideas in Listing 5-1—it just prints numbers zero, one, or more than one. However, one thing you notice is the last case with the underscore (_) wildcard. It matches anything not defined in the cases above it, so it serves the same purpose as the default keyword in Java and C# switch statements. However, if you are unfamiliar with switch statement in Java or C#, don’t worry, it just means that if you try to put a case _before any other case clauses, the compiler will throw an unreachable code error on the next clause as shown in Listing 5-2, because nothing will get past the case _clause so case _serves as default.

Listing 5-2. case _Before Any Other Case Clauses

def printNum(int: Int) {
 int match {
  case _ => println("more than one")
  case 0 => println("Zero")
  case 1 => println("One")

  }
}
<console>:10: warning: unreachable code
         case 0 => println("Zero")
                          ^

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 PrintNum.scala program (see Listing 5-3).

Listing 5-3. PrintNum.scala

object PrintNum {
def printNum(int: Int) {
 int match {
  case 0 => println("Zero")
  case 1 => println("One")
  case _ => println("more than one")
  }
}

}

Compile it with the following line:

scalac -print PrintNum.scala

The result follows:

package <empty> {
  object PrintNum extends Object {
    def printNum(int: Int): Unit = {
      case <synthetic> val x1: Int = int;
      (x1: Int) match {
        case 0 => scala.this.Predef.println("Zero")
        case 1 => scala.this.Predef.println("One")
        case _ => scala.this.Predef.println("more than one")
      }
    };
    def <init>(): PrintNum.type = {
      PrintNum.super.<init>();
      ()
    }
  }
}

Pattern matching, at its core, is a complex set of if/else expressions that lets you select from a number of alternatives. At first glance, if we are allowed to unabashedly assume that you are from Java background, pattern matching looks a lot like Java’s switch statement but you will notice several key differences in even a simplest case. Let’s analyze this by writing one example using both Scala’s pattern matching and Java’s switch statement. Listing 5-4 illustrates the example of calculating Fibonacci numbers.

Listing 5-4. Fibonacci Numbers Using Scala's Pattern Matching

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 as illustrated in Listing 5-5.

Listing 5-5. Java Equivalent of Listing 5-4

public int fibonacci(int in) {
  switch (in) {
    case 0:
     return 0;

     case 1:
      return 1;
     default:
      return fibonacci(in - 1) + fibonacci(in - 2);
   }
}

You will notice the following differences bewteen Listing 5-4 and Listing 5-5.

  • There’s no break statement between cases in Scala, where you need break or return at the end of the case in Java.
  • 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;

We just pointed out several key differences in the Fibonacci example written using both Scala’s pattern matching and Java’s switch statement.

We will now show it gets even better with Scala’s pattern matching by modifying Listing 5-4.

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 as in Listing 5-6.

Listing 5-6. Using Guards in Pattern Matching

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 => 0is 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 and minimize the number of tests, cache test results, and even cache guard results.

Matching Any Type

Let’s consider a list of Any type of element, containing a String, a Double, an Int, and a Char (see Listing 5-7).

Listing 5-7. List of Any Types

val anyList= List(1, "A", 2, 2.5, 'a')

We decide to let the user know of the Int, String, and Double type from the List using the code in Listing 5-8.

Listing 5-8. Matching Elements of Different Types in a List

scala> for (m <- anyList) {
 m match {
 case i: Int => println("Integer: " + i)
 case s: String => println("String: " + s)
 case f: Double => println("Double: " + f)
case other => println("other: " + other)
}
}
Integer: 1
String: A
Integer: 2
Double: 2.5
other: a

Listing 5-8 has a deep implication as you will learn in the section that follows.

Testing Data Types

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 as illustrated in Listing 5-9.

Listing 5-9. Matching Elements of Different Types in a List

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. Now to fully appreciate the power of pattern matching in Scala, we will see the Java equivalent of Listing 5-9.

Listing 5-10. Java Equivalent of Listing 5-9

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();
}

In the Java equivalent code in Listing 5-10, there is a separation between the instance of 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 instance of 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. The same code in Scala is shorter, and there’s no explicit casting. Pattern matching is a powerful way to avoid explicit casting.

Pattern Matching in Lists

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.

Image Note  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.

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] (see Listing 5-11).

Listing 5-11. Using Pattern Matching to Sum Up All the Odd Ints

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 whether 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
}

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.

Pattern Matching and Case Classes

Case classes are classes that get to String, 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 the new keyword.

Let’s define a case class:

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

Let’s create an instance of Person:

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]

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.

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 this code snippet:

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

into the following snippet:

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. 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 ESME1 code:2

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 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 it 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
}

In a broader sense 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.3 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 classBody() 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)
}

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 Collections. The Scala collections library is the most noteworthy library in the Scala ecosystem.

_______________________________

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

2This 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.

3Here is where a unity ped 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