Published using Google Docs
Scala Pattern Matching & Decomposition
Updated automatically every 5 minutes

In this blog post, I will cover various pattern matching variants that I learned in my journey to learn Scala.

I would like introduce val types, tuples, singleton object, case class to help us with the examples for each variant.

// Constants
val TheGood = "The Good"
val TheBad = "The Bad"
val theUgly = "The Ugly"

// A tuple
val tuple = (1, "It is me")

// A singleton object
object Singleton

// A case class
case class Container(x:Any)

// Singleton object with constants
object Constants {
   val pi = "3.14"
   val PI = 3.14
}

Matching constant values:

The following function matches values if x is 3.14 or "3.14". For all other values it raises scala.MatchError exception indicating that, the pattern matching couldn't fall in any case listed.

def matchConstant(x:Any) = x match {
   case 3.14   => "PI"
   case "3.14" => "PI value as String"
}

If you invoke the above function with various values as below:

scala> matchConstant(3.14)
res1: String = PI

scala> matchConstant("3.14")
res2: String = PI value as String

scala> matchConstant(2)
scala.MatchError: 2 (of class java.lang.Integer)

Wildcard matcher:

We got scala.MatchError as there is no case, that could match the input integer 2. We can fix this by using wildcard matcher pattern so that we can prevent the MatchError.

Added wildcard mather to the matchConstant function:

def matchConstant(x:Any) = x match {
   case 3.14   => "PI"
   case "3.14" => "PI value as String"

   // Wildcard matcher
   case _ => "Unknown"
}

Now invocation of matchConstant with integer parameter 2 results as "Unknown"

scala> matchConstant(2)
res5: String = Unknown

Variable matcher pattern

Certain times you may want to match any value you pass-in. It can be achieved using wildcard. Another alternate way to match is variable pattern. The following example illustrate the variable pattern

def matchAnything(x:Any) = x match {
   // Variable pattern
   case n => s"Matched value $n"
}

Invoking the above function:

scala> matchAnything(1)
res40: String = Matched value 1

scala> matchAnything("1")
res41: String = Matched value 1

scala> matchAnything(Singleton)
res42: String = Matched value Singleton$@1d20b21

Variables and constants (also know as Stable identifier pattern)

The val types identifers and singletons fall under this category. Scala language enforces few rules to match stable identifiers.

Let us invoke the matchStableIdentifiers function

scala> matchStableIdentifiers("The Good")
res19: String = The Good

scala> matchStableIdentifiers("The Bad")
res20: String = The Bad

scala> matchStableIdentifiers("The Ugly")
res21: String = The ugly

scala> matchStableIdentifiers(3.14)
res22: String = PI value 3.14

scala> matchStableIdentifiers("3.14")
res23: String = Constants.pi has value 3.14

scala> matchStableIdentifiers(Singleton)
res24: String = The singleton

scala> matchStableIdentifiers("No where to go!")
res25: String = Any value No where to go!

Matching types:

The pattern matching can be applied to based on type of the object. This is similar to instanceof operator in Java.

In the following example we match x to case b:Boolean if x hold a boolean type value. Similarly if x holds a value of double type then case d:Double =>... will be matched.

def matchByType(x: Any) = x match {
   case b:Boolean => s"A boolean value: $b"
   case d:Double => s"An double value: $d"
   case _ => "Unknown type"
}

Let us invoke matchByType function

scala> matchByType(true)
res26: String = A boolean value: true

scala> matchByType(false)
res27: String = A boolean value: false

scala> matchByType(1)
res28: String = Unknown type

scala> matchByType(1.0)
res29: String = An double value: 1.0

Matching types and restricting the data with guards:

Let us extends pattern matching to match specific values of a specific type. In the following example we use pattern matching to print a number is even or odd.

def matchValueOfTypeWithGuard(x: Any) = x match {
   case x:Int if x % 2 == 0 => s"The input is an even number : $x"
   case x:Int if x % 2 != 0 => s"The input is a odd number : $x"
   case _ => "Unknown type"
}

Let us invoke the above function with various values:

scala> matchValueOfTypeWithGuard(2)
res30: String = The input is an even number : 2

scala> matchValueOfTypeWithGuard(3)
res31: String = The input is a odd number : 3

scala> matchValueOfTypeWithGuard("R")
res32: String = Unknown value

Matching types(tuples and other objects) & decompostion:

Let me show examples of another variant in matching types and also decompose them.

Following example, matches a tuple and decomposes it to extract objects used in constructing the tuple.

def matchByTypeAndDecompostion(x : Any) = x match {

   // Matches a tuple and extracts objects used in contructing the tuple
   case (x, y) => s"A tuple with values $x and $y"

   case (x, y, z) => s"A tuple with values $x, $y and $z"

   // Contructor decomposition pattern
   case Container(x) => s"Container with value: $x"

   case _ => "Unknown type"
}

Let us invoke the above function with various values:

scala> matchByTypeAndDecompostion((1, (1,2)))
res36: String = A tuple with values 1 and (1,2)

scala> matchByTypeAndDecompostion((1, (1,2), (1,2,3)))
res37: String = A tuple with values 1, (1,2) and (1,2,3)

scala> matchByTypeAndDecompostion(Container(3))
res38: String = Container with value: 3

scala> matchByTypeAndDecompostion(Container("Hello"))
res39: String = Container with value: Hello

Matching lists & decomposing lists:

Following examples demonistrate pattern matching List objects and also decomposing the list objects

def matchList(x:Any) = x match {
   // matches a List(1,2,3)
   case List(1, 2, 3) => "Matched List(1, 2, 3)"

   // Matches a list with only one element and extracts the element by decompising the list
   case x :: Nil => s"Only one element in the list $x"

   //  Matching a list with single element and extracting the element, can be achieved using constructor decomposition
   //case List(x) => s"Only one element in the list $x"

   // Matches a list with head as integer 2
   case 2 :: xs  => s"A list with head 2 and tail $xs"

   // Matches a list that has integer 3 followed by 4 and other elements.
   // Decomposes list and extract rest of the list excluding the first two elements
   case List(3, 4, xs @ _*) => s"A list with  3, 4 and tail $xs"

   // Decomposing a list as head and tail
   case x :: xs => s"A list with head $x and tail $xs"

   // Matching a empty list or null
   case nil => "Empty List"
   // Matching a empty list using contructor decomposition??
   //case List() =>  "Empty List"
}

Let us invoke the function to match a specific case

scala> matchList(List(1,2,3))
res49: String = Matched List(1, 2, 3)

scala> matchList(List(1))
res50: String = Only one element in the list 1

scala> matchList(List(2,3,4))
res51: String = A list with head 2 and tail List(3, 4)

scala> matchList(List(3,4,5,6,7,8,9))
res52: String = A list with  3, 4 and tail List(5, 6, 7, 8, 9)

scala> matchList(List(4,5,6,7,8,9))
res53: String = A list with head 4 and tail List(5, 6, 7, 8, 9)

scala> matchList(List())
res54: String = Empty List

scala> matchList(null)
res55: String = Empty List

Matching sequences, ranges and decomposing them:

Following example illustrates matching sequences, ranges and decompose them.

def matchSequence[E](x: Seq[E]) = x match {
   case x +: Nil => s"Only element is head: $x"
   case _ :+ x => s"Last element $x"
   case nil => "Empty Sequence"
}

Let us invoke the above function with ranges and sequences

scala> matchSequence( 1 to 3)
res56: String = Last element 3

scala> matchSequence( 1 to 3 take 1)
res57: String = Only element is head: 1

scala> matchSequence( 1 to 3 take 0)
res58: String = Empty Sequence

scala> matchSequence(Seq(1,2,3,4))
res64: String = Last element 4

scala> matchSequence(Seq(1))
res65: String = Only element is head: 1

Pattern matching in catching exceptions

The following example demonistrates catching various exception using pattern matching.

def stringToPositiveIntOnly(s: String) = try {
   val i = s.toInt
   println(s"Input value $i")
   assert(i > 0)
} catch {
   case e: NumberFormatException => "Not a number"
   case e:java.lang.AssertionError => "Assertion failed"
}

Let us invoke the above function with various values

scala> stringToPositiveIntOnly(null)
res84: Any = Not a number

scala> stringToPositiveIntOnly("-1")
Input value -1
res85: Any = Assertion failed

scala> stringToPositiveIntOnly("1")
Input value 1
res86: Any = ()

Defining multiple vals with patterns:

The following examples illustrate pattern matching and decompose them to multiple vals

scala> val (a, b) = (1, ("Ramesh", "NY"))
a: Int = 1
b: (String, String) = (Ramesh,NY)

Pattern matching in generators:

for ( keyAndValue <- Vector(1 -> "a", 2 -> "b") )
   yield keyAndValue._2

for ( (k, v) <- Vector( 1 -> "a",  2 -> "b") )  
   yield v

Decomposing values based on Regular Expressions:

Regular expression can be used to match certain values and decompse the input to extract interesting parts. Notice that the decomposition will be based on the groups matched by regular expression

// Regular Expressions with only one groups
val Yes = "(y|Y)es".r

// Checking regex works
Yes.pattern.matcher("yes").matches
Yes.unapplySeq("yes") will result: Option[List[String]] = Some(List(y))

def catchYes(s:String) = s match {
   case Yes(x) => s
   case _ => None
}

catchYes("yes")
catchYes("Yes")


// Regular Expressions with multiple groups
val Yes = "(y|Y)(es)".r

// Checking regex works
Yes.pattern.matcher("yes").matches
Yes.unapplySeq("yes") will result: Option[List[String]] = Some(List(y, es))

def catchYes(s:String) = s match {
   // As regex has two groups we should have enough parameter to extract
   case Yes(x,y) => s
   case _ => None
}

catchYes("yes")
catchYes("Yes")