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")