Type safe Scala — Don’t use null

Jun 03, 2017
functional-programming
scala

The most dreaded inhabitant.

null is a special value that denotes a reference that points to nowhere, in other words, it’s the “zero value of the references set”. This value hence inhabits all reference types in Scala and Java. It was first implemented by the great Tony Hoare , and called it afterwards his million dollar mistake.

To me, the problem of null is not itself but rather how traditional type systems make use of it. To understand why null exists let’s think of an array of string references, and that we want to remove one of the references (for example to avoid loitering) without performing the costly operation of resizing the array, what reference value should we use when replacing the original reference? In this case we need an special reference value that denotes the zero reference, that’s precisely null.

If null is necessary, why is it so dreaded then? Because traditional imperative type systems implicitly bypass the reference layer for the programmer (this is good for the learning curve but bad for avoiding bugs), causing all sort of trouble when the followed reference is null.

A solution would be if type systems made explicit the use of references, instead of letting us write:

var name: String = "Anler"
name = null
println("Hello " ++ name) // throws java.lang.NullPointerException

They should force us to write:

var name: Reference[String] = Reference("Anler")
name = null
// println("Hello " ++ name) wouldn't compile because name is a
// Reference, not a String
println("Hello " ++ name.getOrElse("no name")) // Hello no name

Because of Scala’s compatibility with Java, we don’t get forced to use an explicit strong reference type, but we still can avoid the null disease by using the Option pattern cure, and the idea is very simple: if we want to express the possibility of absence, we can use something very similar to what we already use for expressing indeterminacy.

For expressing indeterminacy we use lists, defined as:

sealed trait List[+A]
final case object Nil extends List[Nothing]
final case class Cons[A](h: A, t: List[A]) extends List[A]

For expressing possibility of absence we could use also a list, but that’s too unnecessarily powerful (for more information on why that is bad check out Constraints Liberate, Liberties Constrain by Runar Bjarnason), so we better use a list that can only hold one element: Option.

sealed trait Option[+A]
final case object None extends Option[Nothing]
final case class Some[A](a: A) extends Option[A]

Once we got that, we can start using it in all places where we don’t have a value to return, for example:

When performing division, instead of returning an Int or fail with an exception when the divisor is zero, we return Option[Int]:

def divide(a: Int, b: Int): Option[Int] =
if (b == 0) None
else Some(a / b)

When indexing a list, instead of returning a value A for that index or fail with an exception when that index doesn’t exist, we return Option[A]:

def index[A](xs: List[A], i: Int): Option[A] =
try { Some(xs(i)) }
catch { case _ => None }

Thanks to using Option we have transformed what in the beginning was a Partial function into a Total function. See Partial functions .