Scala Tutorial

Basics

Control Statements

OOP Concepts

Parameterized - Type

Exceptions

Scala Annotation

Methods

String

Scala Packages

Scala Trait

Collections

Scala Options

Miscellaneous Topics

Scala Extractors

Scala extractors are objects that have an unapply method. They can be used in pattern matching to destructure objects. Extractors allow for custom patterns beyond simple case class decomposition.

Basics

For an object to be used as an extractor, it should define an unapply method. The method should return an Option containing the decomposed values if they match the expected pattern and None if they don't.

Here's a simple example:

object Even {
  def unapply(x: Int): Option[Int] = if (x % 2 == 0) Some(x) else None
}

val number = 4

number match {
  case Even(n) => println(s"$n is even.")
  case _ => println(s"$number is odd.")
}

In this example, if number is even, the pattern Even(n) will match, and n will bind to the even number.

Extractors with Multiple Values

You can return multiple values by using tuples:

object Name {
  def unapply(str: String): Option[(String, String)] = {
    val parts = str.split(" ")
    if (parts.length == 2) Some(parts(0), parts(1)) else None
  }
}

"John Doe" match {
  case Name(first, last) => println(s"First Name: $first, Last Name: $last")
  case _ => println("Name didn't match the pattern.")
}

Extractor with unapplySeq

For sequences, unapplySeq can be defined:

object Names {
  def unapplySeq(str: String): Option[Seq[String]] = {
    val parts = str.split(" ")
    if (parts.length > 0) Some(parts) else None
  }
}

"John Jacob Jingleheimer Schmidt" match {
  case Names(first, rest @ _*) => println(s"First Name: $first, Other Names: ${rest.mkString(", ")}")
}

In this example, the @_* is a pattern that matches the rest of the sequence.

Benefits

  1. Reusability: Extractors can be reused across multiple classes and are not tied to a particular class, unlike case class decomposition.

  2. Abstraction: Extractors allow abstracting away the details of object construction and decomposition. This is useful in scenarios where how you want to pattern match an object is different from how it's constructed.

  3. Flexibility: Since the unapply method returns an Option, extractors have the flexibility to determine which objects they can successfully destructure and which they can't.

In summary, extractors in Scala provide a way to define custom patterns for pattern matching, adding to the expressiveness and flexibility of the language.

  1. Custom Extractors in Scala:

    Define custom extractors for your own data types.

    object Email {
      def unapply(email: String): Option[(String, String)] = {
        val parts = email.split("@")
        if (parts.length == 2) Some(parts(0), parts(1)) else None
      }
    }
    
  2. Extractor Objects in Scala:

    Extractors are often defined as objects with an unapply method.

    object Even {
      def unapply(x: Int): Option[Int] = if (x % 2 == 0) Some(x) else None
    }
    
  3. Unapply Method in Scala Extractors:

    The unapply method in extractors defines the pattern-matching logic.

    object Name {
      def unapply(fullName: String): Option[(String, String)] = {
        val parts = fullName.split(" ")
        if (parts.length == 2) Some(parts(0), parts(1)) else None
      }
    }
    
  4. Extracting Values with Pattern Matching in Scala:

    Use pattern matching to extract values using extractors.

    val email = "john.doe@example.com"
    email match {
      case Email(user, domain) => println(s"User: $user, Domain: $domain")
      case _ => println("Invalid email format")
    }
    
  5. Matching Multiple Values with Extractors in Scala:

    Combine multiple extractors in a single pattern match.

    val input = "John Doe"
    input match {
      case Name(firstName, lastName) => println(s"First: $firstName, Last: $lastName")
      case Email(user, domain) => println(s"User: $user, Domain: $domain")
      case _ => println("Unknown pattern")
    }
    
  6. Using Extractors for Case Class Deconstruction in Scala:

    Case classes automatically have an extractor generated for them.

    case class Person(name: String, age: Int)
    
    val person = Person("John", 25)
    person match {
      case Person(name, age) => println(s"Name: $name, Age: $age")
      case _ => println("Not a Person")
    }