a site about software

Bottom types, or “Why would Java be better off without null?”

When I first heard the idea that Java would be better off without null, I looked around for an explanation I could understand. Unfortunately, everything I found used code examples from languages without null. If I knew those languages, I wouldn’t be searching.

I wound up learning the “hard way” but now I figure I can answer the question using Java examples. Let’s jump right in.

Code Example 1

A and B are classes, not interfaces, so no Java class can extend both. There’s no way the method() can return a meaningful answer. It could return null, but not a real object. So let’s rename method() to undefined().

Think about how you might implement undefined(). Is it even possible to get this code to compile? If you replaced the method calls with null, it would obviously work.

But methods can’t say they return null.

We’d need a type there instead. Ok, so what is the type of null? Let’s look at another example.

Code Example 2

Here we’re using the same code “throw new RuntimeException()” in two contexts that expect two incompatible types. Ok, so what is the type of “throw new RuntimeException()” ?

The answer is: they’re both a form of the bottom type, which is logically a subtype of all other types. It’s the logical opposite of Object, the top of the Java hierarchy. It’s a fairly weak form of the bottom type without a name. Let’s say we enhance Java by giving it the name Nothing. This code would compile:

How can we fill in the implementation? We’ve already seen two ways: return null, or throw.

How else? We can recurse in an infinite loop.

How else? We can cast.

This code would compile but throw a ClassCastException at runtime, so it’s logically the same thing as throw new RuntimeException().

Of these choices, throwing is already really useful for pre-conditions. It’s idiomatic to call the method “error” when it takes a String parameter, and “undefined” without any parameters.

Since Java doesn’t have the Nothing type, we just use void today. In the following code example, Nothing is slightly more accurate than void, but it doesn’t really matter since the return type isn’t used.

But the point of a bottom type is you can return it.

This is pretty much what Scala has. Its bottom type is called Nothing. Haskell has a bottom type but leaves it unnamed. To see why, let’s look at my first question again.

Is it possible to get this code to compile? Yes, we can implement undefined() without the Nothing type, as follows.

More on this later.

Optionality

In Java, we use null for more than just indicating an error or from unreachable code. We also use null to represent optionality.

If map doesn’t contain key, value will be null. What about the reverse? Is it true that when value is null, map doesn’t contain key? Nope, because map may contain the entry (key -> null), in which case we need to remember to check map.containsKey(key).

In languages without null, we need another way to represent optionality.

Now methods that sometimes return null could be refactored to return Option instead. For example, Map.get(K) could return Option<V>. Calling None.get() throws, which is logically equivalent to calling a method on null and getting a NullPointerException. If that were the whole story, Option wouldn’t be very useful, but it’s normal to add additional functionality. For example, Option.getOrElse(T default) could return Some.value for Some and default for None. Another example, forEach(block) could execute the code block on Some.value and just do nothing on None.

Putting it all together

The two concepts (bottom types and Options) come together in None.get(). Since it never returns a real value, it could return the bottom type. The fact that None is generic on type T and get() returns T makes the method look just like our earlier implementation of undefined(). With type variance, we could make None into a Singleton implementing Option<Nothing>.

Methods like Map.get() could return the Singleton.

With Option, we can get rid of null from the language completely. But should we? On the JVM, probably not. The Option wrapper objects waste a lot of memory. Let’s say we have a Person class with field private final Address workAddress which may or may not exist. Replacing the Address with Option<Address> adds an extra 16 bytes per non-null workAddress (on a 64 bit JVM). Maybe this cost could be optimized away in a future version of the JVM but we’d pay it today.

However, there are big benefits of consistently using Option instead of null. Using the type system to reflect optionality communicates more about your API, plus it prevents common mistakes. It trades one kind of syntax cruft for another. I think it’s a clear win in languages with lambdas and so Option will be more compelling with Java 8. You can see the difference with Scala today.

Scala and Haskell

Scala actually has two bottom types. Nothing works just like the examples we’ve seen so far.

Scala can even infer Nothing as the return type in this example.def undefined: Nothing = throw new RuntimeException
def undefined = throw new RuntimeException

Scala has a second bottom type called Null.

def undefined: Null = null

Why? Primitives.

So Nothing and Null are both bottom types in Scala, and Nothing is “below” Null. In the Java version public static <T> T undefined() we can’t differentiate between non-normal termination and null. But in Scala we can, because it’s a compiler error to return null from a method declared to return Nothing.

Haskell doesn’t have this problem because it doesn’t have any of the complicating factors we’ve gone over. Haskell doesn’t have type casts, primitives, or null! That means the bottom type can only recurse infinitely or throw. Without null, there’s no need for two bottom types. In fact, Scala only names Nothing to differentiate it from Null. Without two bottom types, Haskell gets by without naming the bottom type (using the generics trick alone).

a is a generic type like Java’s <T>.[Char] is a list of Char, in other words, a String.

So in Haskell, the method signature undefined :: a is enough to know with certainty that the body doesn’t return a value. Haskell’s type system is very strict and precise, so there are many examples like this where the type signature alone tells you everything you need to know about the function.

Future versions of Java will never eliminate null, but remember that Option is a good option.