Update 2: Thanks to @SeanTAllen I have fixed a typo in one of the code examples.
Update: Thanks to @xuwei_k and @eed3si9n I have learned that value classes which mix in a universal trait incur the cost of allocation. Therefore I had to change the second example (NameOpt), which – by the way – lead to significant LOC reduction 😉
The recently released milestone M5 for Scala 2.11.0 contains a nice little gem: extractors which don’t need any allocations. This should improve performance significantly and make extractors ready for prime time.
So far an extractor had to be an object with an unapply method taking an arbitrary argument and returning an Option, potentially destructing the given argument into a value of a different type:
def unapply(any: A): Option[B]
There are variations, e.g. to extract several values or sequences of such, but the basic shape remains the same. Here is a simple example:
object PositiveInt { def unapply(n: Int): Option[Int] = if (n > 0) Some(n) else None }
Extractors are quite useful to write concise and expressive code. Yet unapply returning an Option might have a negative impact on runtime performance, because an instance of Some needs to be created for each successful extraction.
Scala 2.11 introduces name based extractors which no longer require unapply to return an Option. Instead any object which defines the two methods isEmpty and get will do the job:
isEmpty: Boolean get: A
Clearly this could be an Option, but we can also make use of value classes which have been introduced in Scala 2.10. Value classes, which extend from AnyVal, don’t get allocated, because all operations are inlined by the compiler. Let’s rewrite the above simple example:
class PositiveIntOpt(val n: Int) extends AnyVal { def isEmpty: Boolean = n <= 0 def get: Int = n } object PositiveInt { def unapply(n: Int): PositiveIntOpt = new PositiveIntOpt(n) }
Let’s give it a spin in the REPL:
scala> val PositiveInt(n) = 1 n: Int = 1 scala> val PositiveInt(n) = 0 scala.MatchError: 0 (of class java.lang.Integer)
Woot, that works! Here is another example, this time extracting multiple values. We are making use of value classes, universal traits (extending Any) and the null object pattern to minimize allocations:
class NameOpt(val parts: (String, String)) extends AnyVal { def isEmpty: Boolean = parts == null def get: (String, String) = parts } object Name { def unapply(name: String): NameOpt = { val parts = name split " " if (parts.length == 2) new NameOpt((parts(0), parts(1))) else new NameOpt(null) } }
Of course, this adds quite a bit boilerplate compared to simply returning an Option. But the performance gain might be worth it.
If we control the class we want to destruct, we can even add the extractor logic to the class itself, thereby avoiding any allocations at all. In this case, in order to extract multiple values, we have to define the according product selectors _1, _2, etc.:
object Name { def unapply(name: Name) = name } class Name(first: String, last: String) { def isEmpty: Boolean = false def get: Name = this def _1: String = first def _2: String = last }
All right, that’s it. Before you start using name based extractors, please make sure to check the next milestones and finally the 2.11 release for any changes to this new feature.