Introduction to Scala – Pattern Matching | Case Classes | Sealed types | Pattern guards

If You are interested to learn about the Scala Try catch Block

Pattern matching is the second most widely used feature of Scala, after function values and closures.Scala feature. Pattern matching is well supported in message processing by Scala.

Each of the choices in a pattern match begins with the keyword case. Each option consists of a pattern, one or more expressions, and an evaluation of the expressions based on whether the pattern matches. The pattern is distinguished from the expressions by the arrow sign =>.

The example programme below demonstrates 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

A function that converts integers to strings is defined in the block with the case statements. Applying a function to an object—like the pattern matching function mentioned above—is made simple by the match keyword.

Try the example programme below to see how it compares a value to several patterns.

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: " + age + " year, name: " + name + "?")
         }
      }
   }
   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?

The compiler automatically adds a variety of helpful features when the case keyword is used. The keyword alludes to a connection between pattern matching and case expressions.

Initially, the function Object() { [native code] } arguments are automatically turned into immutable fields by the compiler (vals). The keyword val is not required. Use the var keyword if you want fields that can be changed. Hence, the lists of function Object() { [native code] } arguments are now shorter.

Second, using the fields supplied as function Object() { [native code] } arguments, the compiler automatically adds the equals, hashCode, and function toString() { [native code] } methods to the class. Our own function toString() { [native code] }() functions are thus no longer required.

Also, since there are no methods that need to be defined, the body of the Person class likewise becomes empty.

Pattern guards

Pattern guards are boolean expressions which are used to make cases more specific. Just add if <boolean expression> after the pattern.

  • Scala 2
  • Scala 3
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
  notification match {
    case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
      "You got an email from special someone!"
    case SMS(number, _) if importantPeopleInfo.contains(number) =>
      "You got an SMS from special someone!"
    case other =>
      showNotification(other) // nothing special, delegate to our original showNotification function
  }
}

val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com")

val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")

println(showImportantNotification(someSms, importantPeopleInfo)) // prints You got an SMS from 123-4567! Message: Are you there?
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone!

println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone!
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String =
  notification match
    case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
      "You got an email from special someone!"
    case SMS(number, _) if importantPeopleInfo.contains(number) =>
      "You got an SMS from special someone!"
    case other =>
      showNotification(other) // nothing special, delegate to our original showNotification function

val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com")

val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")

println(showImportantNotification(someSms, importantPeopleInfo)) // prints You got an SMS from 123-4567! Message: Are you there?
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone!

println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone!

In the case Email(sender, _, _) if importantPeopleInfo.contains(sender), the pattern is matched only if the sender is in the list of important people.

Matching on type only

You can match on the type like so:

  • Scala 2
  • Scala 3
sealed trait Device
case class Phone(model: String) extends Device {
  def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
  def screenSaverOn = "Turning screen saver on..."
}

def goIdle(device: Device): String = device match {
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn
}
sealed trait Device
case class Phone(model: String) extends Device:
  def screenOff = "Turning screen off"

case class Computer(model: String) extends Device:
  def screenSaverOn = "Turning screen saver on..."


def goIdle(device: Device): String = device match
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn

Depending on what kind of Device it is, def goIdle behaves differently. When the case needs to invoke a method on the pattern, this is helpful. It is customary to identify cases by the initial letter of the type (in this case, p and c).

styrene kinds

You might have noticed that the basic types in the aforementioned examples are qualified with the word sealed. Due to the compiler’s check that the match expression’s cases are exhaustive when the underlying type is sealed, this adds an extra layer of safety.

For instance, the compiler issues the following warning if we fail to include VoiceRecording in the method show Notification described above:

  • Scala 2
  • Scala 3
def showNotification(notification: Notification): String = {
  notification match {
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
  }
}
def showNotification(notification: Notification): String =
  notification match
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"

This definition produces the following warning:

match may not be exhaustive.

It would fail on pattern case: VoiceRecording(_, _)

Even instances of input that would not pass are provided by the compiler!

On the other hand, exhaustivity checking mandates that you declare all of the base type’s subtypes in the same file as the base type (otherwise, the compiler would not know what are all the possible cases). Attempting to specify a new type of notification outside of the file defining the sealed trait notification, for example, will result in a compilation error:

case class Telepathy(message: String) extends Notification
           ^
        Cannot extend sealed trait Notification in a different source file

Sequences

Elements make up rays, lists, and vectors. Patterns are also created using these sequences and their constituent parts.

Moreover, we typically wish to describe the dynamic portions of the pattern using wildcards:

We’re going to use the underscore wildcard to match a single element.

The default clause, which also employs the underscore character, should not be confused with this. The element may alternatively be represented by an alias.

On the other side, we’ll utilise the star wildcard * to match a variable number of components (zero, one, or more).

Let’s examine how a sequence appears as a pattern:

def sequencesPatternMatching(sequence: Any): String = {
  sequence match {
    case List(singleElement) => s"I'm a list with one element: $singleElement"
    case List(_, _*) => s"I'm a list with one or multiple elements: sequence"
    case Vector(1, 2, _*) => s"I'm a vector: $sequence"
    case _ => s"I'm an unrecognized sequence. My value: $sequence"
  }
}Copy

We’ve defined the single element of the List using an alias, single Element, in the first case clause:

By using the underscore character in the other case clauses, we are essentially disregarding the values.

The underscore character can be used in match expressions when a specific value is disregarded, in addition to default case clauses.

Tuples

Tuples are objects that only include a few sub-things. We can picture those as small groups of various elements.

Let’s now have a look at a pattern matching example using tuples:

def tuplesPatternMatching(tuple: Any): String = {
  tuple match {
    case (first, second) => s"I'm a tuple with two elements: $first & $second"
    case (first, second, third) => s"I'm a tuple with three elements: $first & $second & $third"
    case _ => s"Unrecognized pattern. My value: $tuple"
  }
}Copy

Using the names specified in the tuple patterns, we have retrieved the elements in this example. In the tuple pattern, we will declare a local variable if we wish to use the first element of the tuple. We may identify those variables as first, second, and third in the example above.

Introduction to Scala – Pattern Matching | Case Classes | Sealed types | Pattern guards
Show Buttons
Hide Buttons