Scala Goodness: Extractors

One Scala goodness is the unapply method to extract values.

Suppose we want to match a string if it does contain DogFood. In Scala we can use an object or class with an unapply method, which takes the input we try to extract values from and returns the extracted value. Because there needn't be a match in every case, the value is wrapped in an Option. A simple extractor for DogFood might look like this:

object DogFood {
  def unapply(f:String):Option[String] = 
    if (f=="meat") Some("MEAT!") else None

// Prints "Yeah MEAT!"
"meat" match { 
  case DogFood(f) => println ("Yeah " + f) 
  case _ => println ("Bah")
// Prints "Bah"
"fruit" match { 
  case DogFood(f) => println ("Yeah " + f) 
  case _ => println ("Bah")

The unapply method can be used in more contexts. Another usecase is to filter for comprehensions. The same DogFood object from above can be used to filter a list of Strings and only return Strings that contain dog food:

scala> val foods = List("meat","fruit","grain")
foods: List[java.lang.String] = List(meat, fruit, grain)

scala> for (DogFood(f) <- foods) yield f
res29: List[String] = List(MEAT!)

Unapply can extract more than one value. In such a case, the method needs to return a Tuple (Javaish):

case class Food(food:String)
case class Name(name:String)

object Dog {
  def unapply(desc:String):Option[(Name,Food)] = {
    val i=desc.indexOf(" eats ") 
    if (i> -1) 
      Some((Name(desc.substring(0,i)), Food(desc.substring(i+6)))) 
    else None

We can now use the Dog object to extract more than one value:

scala> "Brutus eats meat" match { case Dog(f,n) => (f,n) }
res5: (Name, Food) = (Name(Brutus),Food(meat))

This also works for assignment, just as with Tuple assignment:

scala> val Dog(f,n) = "Brutus eats meat"
f: Name = Name(Brutus)
n: Food = Food(meat)

Extractors in Scala even work with Squences. For an example see Daily Scala, a wonderful blog with Scala tips.

See also: