Scala: Pattern Matching
Pattern Matching is one of the most powerful and most widely used feature in Scala. Scala’s Pattern Matching is often compared to Java’s Switch construct, but the capabilities that Scala provides with Pattern Matching is more interesting and far better than traditional Switch. apply
and unapply
are two important concepts one needs to understand before deep diving into Pattern Matching.
Apply and Unapply
Scala lets one extend the function call syntax to values other than functions. Using the apply
method of a companion object is a common Scala idiom for constructing objects. For example, consider a Person
class and it’s companion object
class Person(val name: String, val age: Int)object Person {
def apply(name: String, age: Int): Person = new Person(name, age)
}
Then one can create an object without calling the new
keyword.
val john = Person("John", 28)
Internally, it is calling the apply
method of Person
companion object to create new Person
object.
val john = Person.apply("John", 28)
The unapply
method can be treated as the opposite of the apply
method of a companion object. An apply
method takes construction parameters and turns them into an object, whereas the unapply
method takes an object and extracts values from it, usually the values from which the object was constructed.unapply
method always return an Option
type, it returns either Some[T]
(if it could successfully extract the parameter from the given object) or None
, which means that the parameters could not be extracted.
Extending the Person
companion object to implement unapply
object Person {
def apply(name: String, age: Int): Person = new Person(name, age)
def unapply(person: Person): Option[(String, Int)] = {
if (person.age == 0) None
else Some((person.name, person.age))
}
}
Now, we can extract out name and age from the Person
object like this
val Person(name, age) = johnprintln(s"Hi, my name is $name and I am $age years old!")
Any object which implements the unapply
method is called as Extractor and they are widely used in Pattern matching. Extractors provides a way to extract something from a type you have no control over or if one needs additional ways of pattern matching against a certain data.
Case Class vs Class
A case class
in Scala, by default implements these apply
and unapply
methods. Therefore, one can refactor the Person
class to a case class
case class Person(name: String, age: Int)
val john = Person("John", 28)
val Person(name, age) = john
println(s"Hi, my name is $name and I am $age years old!")
A Better Switch
Scala’s match
construct looks very similar to switch
construct used in Java or C++.
def toYesOrNo(choice: Int): String = choice match {
case 1 => "Yes"
case 0 => "No"
case _ => "Meh"
}
It can also be used to assert types, instead of using isInstanceOf
operator
obj match {
case x: Int => x
case s: String => Integer.parseInt(s)
case _: BigInt => Int.MaxValue
case _ => 0
}
It can be extended to add a Guard clause to a pattern
obj match {
case x: Int if x > 25 => "Greater than 25"
case x: Int if x <= 25 => "Lesser than or equal to 25"
case _ => "Meh"
}
Since case class
defines unapply
method,
trait Tree
case object Leaf extends Tree
case class Node(elm: String, left: Tree, right: Tree) extends Tree// ::: operator of list is used to prefix list.
def inOrder(node: Tree): List[String] = {
node match {
case Node(e, Leaf, Leaf) => List(e)
case Node(e, l, Leaf) => inOrder(l) ::: List(e)
case Node(e, Leaf, r) => List(e) ::: inOrder(r)
case Node(e, l, r) => inOrder(l) ::: List(e) ::: inOrder(r)
}
}
Because of the unapply
method, it is possible to create patterns on domain objects and use them in pattern matching.