Livan Frometa bio photo

Livan Frometa

LinkedIn Github Twitter

Playing with pattern matching and extractor objects in Scala

I want to show you two powerful syntax that the scala language has.

We will start with the Pattern matching, this syntax in used when we want to match one or more patterns in an expression such as a constant pattern, variable pattern, constructor pattern, sequence pattern, tuple pattern or type pattern.

Pattern matching

Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the switch statement in Java and it can likewise be used in place of a series of if/else statements.

The following examples show many different types of patterns that can be used:

def numberAsLiteralString(value: Int): String = value match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "many"
}

numberAsLiteralString(1) // "one"
numberAsLiteralString(5) // "many"

Pattern guards

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

val maybeValue = Option(2)
val result = maybeValue match {
  case Some(value) if value == 2 => s"My value is `2`"
  case Some(value)               => s"My value is $value"
  case None if value == 2        => s"I have not value"
}
// res0: result: String = "My value is `2`"

The pattern matching statement of Scala is very useful to make matches in algebraic types expressed by case classes, it also allows us to define patterns independently of case classes, using unapply methods in extractor objects.

Extractor objects

An extractor object is an object with an unapply method. Whereas the apply method is like a constructor which takes arguments and creates an object, the unapply takes an object and tries to give back the arguments. This is most often used in pattern matching and partial functions.

object Int {
  def unapply(arg: String): Option[Int] = Try(arg.toInt).toOption
}

def matchingNumberByString(value: String): Int = value match {
  case "zero" => 0
  case "one"  => 1
  case "two"  => 2
  case Int(i) => i
  case _      => throw new RuntimeException()
}

matchingNumberByString("one") // 1
matchingNumberByString("6")   // 6

Then clarify some concepts mentioned above to better understand how we can exploit these powerful Scala syntax.

Constant patterns

Any literal can be used as a constant. A constant pattern can only coincide with itself, this means that if we declare a variable with value 2 it will only coincide with an Int value of 2. e.g:

val value: Int = 2
  val result: String = value match {
    case 2 => "two"
    case 3 => "three"
  }
// res0: result: String = "two"

Variable patterns

A variable pattern matches any object just like the wildcard character. For example, at the end of a match expression, you can use the wildcard character to match any type or capture the value in a variable. e.g:

val value: Int = 2
val result: String = value match {
  case _ => "any"
}
// res0: result: String = "any"

Or you can write this instead:

val value: Int = 2
val result: String = value match {
  case foo => s"My value is $foo"
}
// res0: result: String = "My value is 2"

Constructor patterns

The constructor pattern lets you match a constructor in a case statement. As shown in the examples, you can specify constants or variable patterns as needed in the constructor pattern:

trait Person {
  val name: String
  val age: Int
}

case class Student(name: String, age: Int, averageScore: Double) extends Person

case class Teacher(name: String, age: Int, totalOfClasses: Int) extends Person

val person: Person = Student("Martin", 17, 4.75)
val result: String = person match {
    case Student(name, _, averageScore) => s"The student $name has a average score $averageScore"
    case Teacher("John", age, _)        => s"The teacher John has $age years old"
  }
// res0: result: String = "The student Martin has a average score 4.75"

Type patterns

A match for a specific type

val valueAny: Any = "2"
val result5: String = valueAny match {
  case v: Int    => s"The value $v is a Int"
  case v: String => s"The value $v is a String"
}
// res0: result: String = "The value 2 is a String"

Sequence patterns

You can match against sequences like List, Array, Vector or others. Use the _ character to stand for one element in the sequence and use _* to stand for “zero or more elements” e.g:

  val sequence: Iterable[Int] = List(1, 2, 3, 4)
  val result: String = sequence match {
  case List(0, _, _)      => "A three element list with 0 as the first element"
  case list @ List(1, _*) => s"A list beginning with 1, having any number of elements: [$list]"
  case Vector(1, _*)      => "A vector beginning with 1 and having any number"
}
  // res0: result: String = "A list beginning with 1, having any number of elements [List(1, 2, 3, 4)]"

Tuple patterns

We can match tuple patterns and access the value of each element in the tuple. You can also use the _ wildcard if you’re not interested in the value of an element e.g:

val tuple: Any = (1, 2)
val result7: String = tuple match {
  case (a, b)       => s"A two elem tuple, with values $a, and $b"
  case (a, b, c)    => s"A three elem tuple, with values $a, $b, and $c"
  case (a, b, c, _) => s"A four elem tuple: got $a, $b, $c and more element"
}
// res0: result: String = "A two elem tuple, with values 1, and 2"

Adding variables to patterns

At times you may want to add a variable to a pattern. You can do this with the following general syntax:

value match {
  case variableName @ pattern
}

You know that you can do this

value match {
  case list: List[_] => s"The List: $list"
}

so it seems like you should try this with a sequence pattern:

value match {
  case list: List(1, _*) => s"The List: $list"
}

Unfortunately, this fails with the following compiler error:

Error:(72, 21) '=>' expected but '(' found.
    case list : List(1, _*) => s"The List: $list"

The solution to this problem is to add a variable-binding pattern to the sequence pattern:

value match {
  case list @ List(1, _*) => s"The List: $list"
}

or

value match {
  case s @ Student(name, _, averageScore) => s"The student $name has a average score $averageScore: student = [$s]"
}

I hope it has been very helpful for you, if you have arrived here, it means that you have been able to understand these two powerful Scala syntax and the associated concepts. You can find the examples of this post here.

See Also

The Scala Cookbook


You can find the Scala Cookbook at these locations: