Scala - Pattern Matching



Pattern matching is the second most widely used feature of Scala, after function values and closures. Scala provides great support for pattern matching, in processing the messages.

A pattern match includes a sequence of alternatives, each starting with the keyword case. Each alternative includes a pattern and one or more expressions, which will be evaluated if the pattern matches. An arrow symbol => separates the pattern from the expressions.

Try the following example program, which shows how to match against an integer value.

Example

object Demo {
   def main(args: Array[String]) {
      println(matchTest(3))
   }
   
   def matchTest(x: Int): String = x match {
      case 1 => "one"
      case 2 => "two"
      case _ => "many"
   }
}

Save the above program in Demo.scala. The following commands are used to compile and execute this program.

Command

\>scalac Demo.scala
\>scala Demo

Output

many

The block with the case statements defines a function, which maps integers to strings. The match keyword provides a convenient way of applying a function (like the pattern matching function above) to an object.

Try the following example program, which matches a value against patterns of different types.

Example

object Demo {
   def main(args: Array[String]) {
      println(matchTest("two"))
      println(matchTest("test"))
      println(matchTest(1))
   }
   
   def matchTest(x: Any): Any = x match {
      case 1 => "one"
      case "two" => 2
      case y: Int => "scala.Int"
      case _ => "many"
   }
}

Save the above program in Demo.scala. The following commands are used to compile and execute this program.

Command

\>scalac Demo.scala
\>scala Demo

Output

2
many
one

Matching using Case Classes

The case classes are special classes that are used in pattern matching with case expressions. Syntactically, these are standard classes with a special modifier: case.

Try the following, it is a simple pattern matching example using case class.

Example

object Demo {
   def main(args: Array[String]) {
      val alice = new Person("Alice", 25)
      val bob = new Person("Bob", 32)
      val charlie = new Person("Charlie", 32)
   
      for (person <- List(alice, bob, charlie)) {
         person match {
            case Person("Alice", 25) => println("Hi Alice!")
            case Person("Bob", 32) => println("Hi Bob!")
            case Person(name, age) => println(
               "Age: " &plus; age &plus; " year, name: " &plus; name &plus; "?")
         }
      }
   }
   case class Person(name: String, age: Int)
}

Save the above program in Demo.scala. The following commands are used to compile and execute this program.

Command

\>scalac Demo.scala
\>scala Demo

Output

Hi Alice!
Hi Bob!
Age: 32 year, name: Charlie?

Adding the case keyword causes the compiler to add a number of useful features automatically. The keyword suggests an association with case expressions in pattern matching.

First, the compiler automatically converts the constructor arguments into immutable fields (vals). The val keyword is optional. If you want mutable fields, use the var keyword. So, our constructor argument lists are now shorter.

Second, the compiler automatically implements equals, hashCode, and toString methods to the class, which use the fields specified as constructor arguments. So, we no longer need our own toString() methods.

Finally, also, the body of Person class becomes empty because there are no methods that we need to define!

Pattern Matching with Sealed Traits

Sealed traits ensure that all implementations of the trait are known at compile-time. So pattern matching is more exhaustive and safer. Using sealed traits can help the compiler check for missing cases in pattern matching.

Example

sealed trait Animal
case class Dog(name: String) extends Animal
case class Cat(name: String) extends Animal
case object Parrot extends Animal

object Demo {
   def main(args: Array[String]) = {
      val animals: List[Animal] = List(Dog("Rex"), Cat("Whiskers"), Parrot)
      animals.foreach {
         case Dog(name) => println(s"Dog: $name")
         case Cat(name) => println(s"Cat: $name")
         case Parrot => println("Parrot")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Dog: Rex
Cat: Whiskers
Parrot

Pattern Matching with Option

Option is a container type that represents optional values. Pattern matching is used to handle Option values. So, it is easy to work with missing values.

Try following example of pattern matching with Option -

Example

object Demo {
   def main(args: Array[String]) = {
      val someValue: Option[Int] = Some(42)
      val noneValue: Option[Int] = None

      println(showOption(someValue))  
      println(showOption(noneValue))  
   }

   def showOption(opt: Option[Int]): String = opt match {
      case Some(value) => s"The value is $value"
      case None => "No value"
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

The value is 42
No value

Pattern Matching with Tuples

Tuples group multiple values into a single compound value. Pattern matching can be used to decompose tuples into their constituent parts.

Try following example of pattern matching with tuple -

Example

object Demo {
   def main(args: Array[String]) = {
      val tuple = (1, "Scala", true)

      tuple match {
         case (a, b, c) =>
            println(s"Int: $a, String: $b, Boolean: $c")  
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Int: 1, String: Scala, Boolean: true

Pattern Matching with Lists

Pattern matching is a powerful way to process lists for handling different list structures.

Try following example of pattern matching with list -

Example

object Demo {
   def main(args: Array[String]) = {
      val list = List(1, 2, 3, 4)

      list match {
         case Nil => println("Empty list")
         case head :: tail => println(s"Head: $head, Tail: $tail")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Head: 1, Tail: List(2, 3, 4)

Pattern Matching Summary

  • Pattern matching is a powerful feature in Scala. Pattern matching is used extensively for handling various data structures and types.
  • Case classes and sealed traits enhance pattern matching by making the code more readable and type-safe.
  • Pattern matching with Option, tuples, and lists provides elegant solutions for handling common data structures.
Advertisements