Appendix A. Pattern Matching and Extractors

Pattern matching is a powerful tool for control flow in Scala. It is often underused and under-estimated by people coming to Scala from imperative languages.

Let's start with a few examples of pattern matching before diving into the theory. We start by defining a tuple:

scala> val names = ("Pascal", "Bugnion")
names: (String, String) = (Pascal,Bugnion)

We can use pattern matching to extract the elements of this tuple and bind them to variables:

scala> val (firstName, lastName) = names
firstName: String = Pascal
lastName: String = Bugnion

We just extracted the two elements of the names tuple, binding them to the variables firstName and lastName. Notice how the left-hand side defines a pattern that the right-hand side must match: we are declaring that the variable names must be a two-element tuple. To make the pattern more specific, we could also have specified the expected types of the elements in the tuple:

scala> val (firstName:String, lastName:String) = names
firstName: String = Pascal
lastName: String = Bugnion

What happens if the pattern on the left-hand side does not match the right-hand side?

scala> val (firstName, middleName, lastName) = names
<console>:13: error: constructor cannot be instantiated to expected type;
found   : (T1, T2, T3)
required: (String, String)
   val (firstName, middleName, lastName) = names

This results in a compile error. Other types of pattern matching failures result in runtime errors.

Pattern matching is very expressive. To achieve the same behavior without pattern matching, you would have to do the following explicitly:

  • Verify that the variable names is a two-element tuple
  • Extract the first element and bind it to firstName
  • Extract the second element and bind it to lastName

If we expect certain elements in the tuple to have specific values, we can verify this as part of the pattern match. For instance, we can verify that the first element of the names tuple matches "Pascal":

scala> val ("Pascal", lastName) = names
lastName: String = Bugnion

Besides tuples, we can also match on Scala collections:

scala> val point = Array(1, 2, 3)
point: Array[Int] = Array(1, 2, 3)

scala> val Array(x, y, z) = point
x: Int = 1
y: Int = 2
z: Int = 3

Notice the similarity between this pattern matching and array construction:

scala> val point = Array(x, y, z)
point: Array[Int] = Array(1, 2, 3)

Syntactically, Scala expresses pattern matching as the reverse process to instance construction. We can think of pattern matching as the deconstruction of an object, binding the object's constituent parts to variables.

When matching against collections, one is sometimes only interested in matching the first element, or the first few elements, and discarding the rest of the collection, whatever its length. The operator _* will match against any number of elements:

scala> val Array(x, _*) = point
x: Int = 1

By default, the part of the pattern matched by the _* operator is not bound to a variable. We can capture it as follows:

scala> val Array(x, xs @ _*) = point
x: Int = 1
xs: Seq[Int] = Vector(2, 3)

Besides tuples and collections, we can also match against case classes. Let's start by defining a case representing a name:

scala> case class Name(first: String, last: String)
defined class Name

scala> val name = Name("Martin", "Odersky")
name: Name = Name(Martin,Odersky)

We can match against instances of Name in much the same way we matched against tuples:

scala> val Name(firstName, lastName) = name
firstName: String = Martin
lastName: String = Odersky

All these patterns can also be used in match statements:

scala> def greet(name:Name) = name match {
  case Name("Martin", "Odersky") => "An honor to meet you"
  case Name(first, "Bugnion") => "Wow! A family member!"
  case Name(first, last) => s"Hello, $first"
}
greet: (name: Name)String

Pattern matching in for comprehensions

Pattern matching is useful in for comprehensions for extracting items from a collection that match a specific pattern. Let's build a collection of Name instances:

scala> val names = List(Name("Martin", "Odersky"), 
  Name("Derek", "Wyatt"))
names: List[Name] = List(Name(Martin,Odersky), Name(Derek,Wyatt))

We can use pattern matching to extract the internals of the class in a for-comprehension:

scala> for { Name(first, last) <- names } yield first
List[String] = List(Martin, Derek)

So far, nothing terribly ground-breaking. But what if we wanted to extract the surname of everyone whose first name is "Martin"?

scala> for { Name("Martin", last) <- names } yield last
List[String] = List(Odersky)

Writing Name("Martin", last) <- names extracts the elements of names that match the pattern. You might think that this is a contrived example, and it is, but the examples in Chapter 7, Web APIs demonstrate the usefulness and versatility of this language pattern, for instance, for extracting specific fields from JSON objects.

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

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