The Scala List extractor demystified

25 November 2011

Francisco Canedo

by Francisco Canedo

This article explains and demystifies Scala list extraction, which is a technique for extracting lists from larger lists.

In Scala code, you’ll often come across list extraction, as in, for example, a pattern in a variable definition:

scala> val head :: tail = List(1,2,3) // <--
head: Int = 1
tail: List[Int] = List(2, 3)

or the second pattern (case) in this simple implementation of map:

def map[A, B](list: List[A])(func: A => B): List[B] = list match {
  case Nil => Nil
  case head :: tail => func(head) :: map(tail)(func) // <--
}

What’s happening is that we’re deconstructing a list into its head and tail components using the ‘head :: tail’ pattern; in both cases ‘head’ becomes the first element of the list and ‘tail’ becomes a list that contains the remaining elements of the list. In the first example the head is the Int with value 1 and the tail a list of Int with the values 2 and 3 or rather, the list’s first element and the rest.

You might be asking yourself ‘what’s going on here, is this a trick in the language?’ It’s not, it’s a language feature that everyone can use.

How it works

You can figure out what’s going on from the source of the collections library. But first you need to know the following things:

  • head :: tail’ in a pattern, is the infix version of ‘::(head, tail)

  • a pattern like ‘::(head, tail)’ results in a call to ‘::.unapply(selector)’, where ‘selector’ is the left-hand-side of a match expression (‘list’ before ‘match’ in the second example) or the right-hand-side of a variable definition (‘List(1,2,3)’ in the first). unapply is supposed to return an Option of tuple whose values are assigned to ‘head’ and ‘tail’ respectively

unapply (like apply) is one of those methods that the Scala compiler will use without the method being named and is used specifically for deconstructing objects in pattern matching. This means that there must be a :: object defined somewhere with an unapply method, whose definition is:

object :: {
  def unapply[A](value: List[A]): Option[(A, List[A])] = { … }
  …
}

There is no such thing. So, what’s going on here?

Exploring case classes

It turns out that there’s a case class called :: (in the scala.collection.immutable package) that extends List. One of the many neat things that case classes give you, is a synthetic companion object with an unapply method that can deconstruct instances of that type, for free.

This means that any case class automatically gets everything you need to be able to both construct and deconstruct an instance. Without having to type any boilerplate. For example, you could do the following in the Scala REPL:

scala> case class Person(handle: String, name: String) // define the class
defined class Person

scala> val person = Person("paco", "Francisco") // construct an instance; look mum, no ‘new’
person: Person = Person(paco,Francisco)

scala> val Person(handle, name) = person // deconstruct it
handle: String = paco // a new val with the handle from the person object
name: String = Francisco // and a new val with the name

scala> val handle2 Person name2 = person // and again with infix notation, which looks rather silly in this case
handle2: String = paco
name2: String = Francisco

There, with one line of code, we’ve created a Person class that has a synthetic unapply method that can deconstruct a Person instance into its components. Just like a List.

Mystery solved

Lists can be deconstructed with :: because there is an appropriate case class for that. ‘Hold on,’ you say, ‘doesn’t this mean that deconstructing something with :: will only work with instances of ::?’ Well, you’re right, if you can deconstruct List(…) with ::, that must mean that a List is, somehow, an instance of ::.

Looking at the source code of the collections library, you’ll see that all the methods that instantiate a ‘List’, actually return an instance of ::. It’s just that ::’s toString method insists that it’s a `List, cheeky.

Conclusion

Scala gives you features that help you (and library makers) to build powerful and concise APIs. Although I wouldn’t recommend that everyone start using punctuation characters to name their classes. Learn more from Programming in Scala chapter Case Classes and Pattern Matching.