Scala Tutorial

Basics

Control Statements

OOP Concepts

Parameterized - Type

Exceptions

Scala Annotation

Methods

String

Scala Packages

Scala Trait

Collections

Scala Options

Miscellaneous Topics

Scala | Pattern Matching

Pattern matching is a mechanism for checking a value against a pattern and possibly deconstructing the value into its constituent parts. Scala's pattern matching is quite powerful and versatile, allowing for deep checks and intricate extraction of values from complex structures.

Basic Pattern Matching

You can use pattern matching with the match keyword, followed by a series of case statements for different patterns:

val x: Int = 5

x match {
  case 1 => println("One")
  case 2 => println("Two")
  case 3 => println("Three")
  case _ => println("Something else")
}

In this example, the last case _ acts as a wildcard, matching any value.

Case Class Matching

Pattern matching is especially useful with case classes:

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

def area(shape: Shape): Double = shape match {
  case Circle(r) => Math.PI * r * r
  case Rectangle(w, h) => w * h
}

Here, when we pass a Shape to the area function, Scala matches its actual type (either Circle or Rectangle) and extracts its parameters, allowing us to compute the area accordingly.

Matching on Collections

You can also pattern match on collections like lists:

val list = List(1, 2, 3)

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

Matching with Guards

You can add conditional expressions to your patterns with if:

val number = 5

number match {
  case x if x % 2 == 0 => println(s"$x is even")
  case x => println(s"$x is odd")
}

Type Matching

You can match based on the type of a value:

def describeType(x: Any): String = x match {
  case i: Int => "Integer"
  case s: String => "String"
  case _ => "Unknown type"
}

Extractors

Beyond case classes, Scala allows for custom pattern matching through extractors using unapply or unapplySeq methods.

Variables in Pattern Matching

When a pattern introduces a new variable (like head and tail in the list example), it starts with a lowercase letter. If you're trying to match against an already defined variable, use backticks:

val toMatch = 5
val x = 5

x match {
  case `toMatch` => println("It matches!")
  case _ => println("Doesn't match")
}

Conclusion

Pattern matching in Scala is an incredibly powerful tool that allows for concise, readable, and safe code when processing data structures. By leveraging this feature, developers can write more declarative code, handle various cases elegantly, and avoid many common runtime errors.

  1. Matching on Case Classes in Scala:

    Case classes are commonly used in pattern matching due to their concise structure.

    case class Person(name: String, age: Int)
    
    val person: Person = Person("Alice", 25)
    
    person match {
      case Person("Alice", age) => println(s"Found Alice with age $age")
      case _ => println("Not Alice")
    }
    
  2. Wildcard Patterns in Scala Matching:

    Wildcard _ allows matching any value, useful for ignoring specific cases.

    val value: Int = 42
    
    value match {
      case 42 => println("The Answer")
      case _ => println("Not the Answer")
    }
    
  3. Pattern Matching with Tuples in Scala:

    Tuples can be used for more complex pattern matching scenarios.

    val pair: (String, Int) = ("Alice", 30)
    
    pair match {
      case ("Alice", age) => println(s"Found Alice with age $age")
      case _ => println("Not Alice")
    }
    
  4. Using Guards in Scala Pattern Matching:

    Guards add conditions to pattern matches.

    val age: Int = 25
    
    age match {
      case x if x < 18 => println("Minor")
      case x if x >= 18 => println("Adult")
    }
    
  5. Pattern Matching on Option and Some/None in Scala:

    Pattern matching on Option is a common use case for handling presence or absence of values.

    val maybeValue: Option[Int] = Some(42)
    
    maybeValue match {
      case Some(value) => println(s"Found value: $value")
      case None => println("No value")
    }
    
  6. Pattern Matching with Extractors in Scala:

    Extractors allow more complex patterns using custom objects.

    object Email {
      def unapply(email: String): Option[(String, String)] = {
        val parts = email.split("@")
        if (parts.length == 2) Some((parts(0), parts(1))) else None
      }
    }
    
    val userEmail: String = "user@example.com"
    
    userEmail match {
      case Email(username, domain) => println(s"Username: $username, Domain: $domain")
      case _ => println("Invalid email format")
    }
    
  7. Sealed Traits and Exhaustive Pattern Matching in Scala:

    Sealed traits ensure exhaustive pattern matching by allowing matches only on known subclasses.

    sealed trait Shape
    case class Circle(radius: Double) extends Shape
    case class Rectangle(width: Double, height: Double) extends Shape
    
    val shape: Shape = Circle(5)
    
    shape match {
      case Circle(r) => println(s"Circle with radius $r")
      case Rectangle(w, h) => println(s"Rectangle with width $w and height $h")
    }
    
  8. Deconstructing Values with Case Classes in Scala:

    Case classes allow easy deconstruction of values during pattern matching.

    case class Point(x: Int, y: Int)
    
    val point: Point = Point(3, 5)
    
    point match {
      case Point(x, y) => println(s"Coordinates: x=$x, y=$y")
    }