Scala - Pattern Matching with Variables and Custom Types



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. One aspect of pattern matching is to use variables within patterns. It can bind to values for more expressive code.

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.

Pattern Matching for Variables

Pattern matching can be used to test strings and variables against patterns. These return different results based on the match outcome. The syntax for matching a string and variable -

def typedPatternMatching(any: Any): String = {
  any match {
    case string: String => s"I am a string. My value: $string"
    case integer: Int => s"I am an integer. My value: $integer"
    case _ => s"I am from an unknown type. My value: $any"
  }
}

This function takes the argument of type Any and matches it against different types. It returns a string that describes the matched type and its value.

Example

def typedPatternMatching(any: Any): String = {
  any match {
    case string: String => s"I am a string. My value: $string"
    case integer: Int => s"I am an integer. My value: $integer"
    case _ => s"I am from an unknown type. My value: $any"
  }
}

object Demo {
  def main(args: Array[String]): Unit = {
    val mixedList: List[Any] = List(42, "Scala", 3.14)

    mixedList.foreach { element =>
      println(typedPatternMatching(element))
    }
  }
}

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

Command

> scalac Demo.scala
> scala Demo

Output

I am an integer. My value: 42
I am a string. My value: Scala
I am from an unknown type. My value: 3.14

We pass each element in mixedList to typedPatternMatching function. It matches the element against different types and prints the result.

Stable Identifiers in Pattern Matching

In some cases, you may want to match against a specific value stored in a variable. But, using the variable name in a pattern will bind the variable to the matched value rather than compare against the stored value. You can use stable identifiers to get desired behavior. Stable identifiers must either start with an uppercase letter or be surrounded by backticks.

Example

def mMatch(s: String) = {
    val target: String = "a"
    s match {
        case `target` => println(s"It was $target")
        case _ => println("It was something else")
    }
}

def mMatch2(s: String) = {
    val Target: String = "a"
    s match {
        case Target => println(s"It was $Target")
        case _ => println("It was something else")
    }
}

Here, starting the variable name with a capital letter for the pattern matches the specific value stored in the variable.

Variable Binding in Pattern Matching

When a pattern matches a value, variables in the pattern are bound to corresponding parts of the value.

Example

Following is the example which shows you how to variable binding in pattern matching –

object Demo {
  def main(args: Array[String]): Unit = {
    val maybeValue: Option[Int] = Some(42)

    maybeValue match {
      case Some(value) => println(s"Value is: $value")
      case None => println("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

Value is: 42

Here, the maybeValue variable is matched against the Some and None cases of the Option type, with the value variable binding to the contained integer.

Pattern Matching with Custom Types

You can extend pattern matching to work with custom types and classes. So, you can decompose instances and access their fields directly.

Example

Following is the example which shows you how to use pattern matching with custom type -

sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape

object Demo {
  def main(args: Array[String]): Unit = {
    val shape: Shape = Circle(5.0)

    shape match {
      case Circle(r) => println(s"Circle with radius: $r")
      case Rectangle(w, h) => println(s"Rectangle with width: $w and height: $h")
      case _ => println("Unknown shape")
    }
  }
}

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

Command

> scalac Demo.scala
> scala Demo

Output

Circle with radius: 5.0

In this example, the shape variable is matched against different case classes. With the variables r, w, and h binding to the respective fields of Circle and Rectangle.

Pattern Matching with Variables Summary

  • Pattern matching is used to test values and decompose into constituent parts using patterns.
  • You can match stable identifiers against specific values stored in variables.
  • Variable binding in patterns extract and work with parts of matched values.
  • You can use pattern matching with custom types to access fields and decompose instances.
  • Typed pattern matching simplifies type handling and improves code readability.
Advertisements