Scala: Extractors and Pattern Matching

Scala Extractors
Reading Time: 3 minutes

An extractor in Scala is an object which has an unapply method as one of its members. Often, the extractor object also defines a method apply for building values, but this is not required. An apply method is like a constructor which takes arguments and creates an object, the unapply method takes an object and tries to give back the arguments. The unapply method reverses the construction procedure of the apply method. the unapply method always returns an Option type, it returns either Some[T] (if it could successfully extract the parameter from the given object) or None, which means that the parameters could not be extracted.

Let’s understand this with the help of an example. We have an object which has both apply and unapply method.

object FullName {
  def apply(firstname: String, lastname: String) = {
    firstname + " " + lastname
  } 

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

Here, FullName(“Ayush”, “Hooda”) i.e., FullName.apply(“Ayush”, “Hooda”) will produce an output “Ayush Hooda” and FullName.unapply(“Ayush Hooda”) will produce an output of type Option[(String, String)] i.e., Some((Ayush, Hooda)).

For now, we have covered and concluded on a part that apply method is used to construct an object an unapply method is used to take an object as an input and gives back the arguments.

Return-type of unapply method:

The return type of an unapply should be chosen as follows:

  • If it is just a test, return a Boolean.
  • If it returns a single sub-value of type T, return an Option[T].
  • If you want to return several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)].

Usage of Extractors:

  • Pattern Matching:
    Extractors can be utilized in Pattern Matching. While comparing the Object of an Extractor in Pattern Matching, the unapply method will be executed spontaneously.

     

    object Divisor {
    
      def apply(x: Double): Double = x / 5
    
      def unapply(z: Double): Option[Double] = {
        if (z % 5 == 0) {
          Some(z / 5)
        }
        else {
          None
        }
      }
    }
    
    val divisor = Divisor(50) // divisor will be 10
    divisor match {
    
      // unapply method is called here.
      case Divisor(div) => print("Divisor: " + div)
      case _ => print("Not able to extract properly!!!")
    }

    Here, the divisor object created which holds the value Divisor(50). Now, when the match is being applied onto it, unapply method is being called internally and helps with the cause of pattern matching, i.e., in this very example we match it against Divisor(div) and _ deals with other cases.
    Note: A Case class already has an Extractor in it so, it can be used with Pattern Matching.

  • Testing:
    Extractors can be utilized for testing. In this, a Boolean type is returned. Let us understand this with the help of an example.

     

    case class ModuloCheck(num: Int)
    
    object ModuloCheck {
      def unapply(num: ModuloCheck): Boolean = {
        if(num.num % 5 == 0) {
          true
        } else {
          false
        }
      }
    }
    
    object Testing extends App {
      val num = 10
      val moduloNum = ModuloCheck(num)
      ModuloCheck.unapply(moduloNum) match {
        case testValue if testValue => print(s"${moduloNum.num} is divisible by 5")
        case _ => print(s"${moduloNum.num} is not divisible by 5")
      }
    }

UnapplySeq:

Sometimes, the number of values to extract isn’t fixed and we would like to return an arbitrary number of values, depending on the input. For this use case, you can define extractors with an unapplySeq method which returns an Option[Seq[T]].

Syntax: def unapplySeq(object: X): Option[Seq[T]].

Here, we have an object of type X and this method either returns None, when the object does not match or returns a Sequence of extracted values of type T, enclosed in class Some.

Let us understand this with the help of an example.

object SortedSequence {
  def unapplySeq(seqOfIntegers: Seq[Int]): Option[Seq[Int]] = {
    if(seqOfIntegers == seqOfIntegers.sortWith(_ < _)) {
      Option(seqOfIntegers)
    }
    else {
      None
    }
  }
}

object UnapplySeq extends App {
  val listOfIntegers = List(1, 2, 3, 4, 5)

  // Applying pattern matching
  listOfIntegers match {
    case SortedSequence(a,b,c,d,e) => print(List(a, c, e))
  }
}

Here, we have used the sortWith method of Seq and match case is being applied on Seq[Int] and we can easily pattern match for every member of this very sequence.

Conclusion:

So, to conclude we have seen how Extractors can be useful in pattern matching and testing. Similarly, there can be many more custom use cases of Extractors that you can explore out. We also had explored how we can use unapplySeq for an unknown number of values.

For full Scala tutorial, refer to my Github Repository

References:

Written by 

Ayush is a Software Consultant having more than 11 months of experience. He has knowledge of various programming languages like C, C++, Java, Scala, JavaScript and is currently working on Big Data Technologies like Spark, Kafka, ElasticSearch. He is always eager to learn new and advance concepts in order to expand his horizon and apply them in project development with his existing knowledge. His hobbies includes playing Cricket, Travelling, Watching Movies

Knoldus Pune Careers - Hiring Freshers

Get a head start on your career at Knoldus. Join us!