warpedjavaguy

Imperative by day and functional by night

Hey Null, Don’t Touch My Monads!


Null handling.. we’ve been doing it completely wrong!

Nulls are a big problem! They are a most constant annoyance and the single root cause of all NullPointerExceptions. It is common practice in Java to defensively check for nulls everywhere. It would be nice if they could just go away. But they won’t because we’ve been using them on purpose to denote missing values. Almost every Java API uses nulls in this way.

When we integrate with Java API’s we have to check for nulls. Fortunately, Scala provides the Option monad to help deal with nulls. It is basically a container that can hold two types of values; Some or None (some value or no value). But for Java interoperability purposes, Some can also hold null. Is this a problem? Lets explore..

First, we’ll define a User class like this (overly simple I know)

case class User (name: String)

Now we’ll define a list of users and filter it

val users = List(User("Wendy"), User("Bob"))
users filter { _.name == "Bob" }

This yields List(User(Bob)). So far so good! Now lets redefine the user list by including a null entry.

val users = List(User("Wendy"), null, User("Bob"))
users filter { _.name == "Bob" }

Bang! NullPointerException! Lets fix it by checking for null.

users filter { u => u != null && u.name == "Bob" }

This yields List(User(Bob)) again. Good, but this is ugly. We don’t want to check for nulls. Let’s wrap every element in an Option instance.

users flatMap { Option(_) } filter { _.name == "Bob" }

Option(null) resolves to None and the expression yields List(User(Bob)) again. Nice! Now let’s try and wrap every element in a Some instance instead and see what happens.

users flatMap { Some(_) } filter { _.name == "Bob" }

Some(null) resolves to null and oh no, we get a NullPointerException! This is a problem. We want Some(null) to resolve to None. So lets define our own Some function that overrides this behavior.

def Some[T](x: T) = Option(x)
users flatMap { Some(_) } filter { _.name == "Bob" }

Now Some(null) returns Option(null) which resolves to None and the expression yields List(User(Bob)) as expected. Ok, so we’ve solved the Some(null) problem. Now lets look at another null problem. Lets blindly extract the second user element in the list (the null entry) and print the name property.

val user = users(1)
println(user.name)

The user reference is null and we get a NullPointerException. Oh dread, we didn’t check for null. Let’s do that.

if (user != null) {
  println(user.name)
}

Now we skip the printing of the name if the reference is null. But we don’t want to check for nulls. So lets wrap the null user in an Option and use foreach.

Option(user) foreach { u => println(u.name) }

Option(null) resolves to None and all is good. With our overriding Some function still in scope, we can do the same using Some also.

Some(user) foreach { u => println(u.name) }

Just like before, our call to Some(null) returns Option(null) which resolves to None and we’re good. But we want cleaner code that looks like this:

user foreach { u => println(u.name) }

This results in a compile error because foreach is not a member of User. But we can fix that by making our overriding Some function implicit. With this implicit in scope, Scala converts user to Option(user) and the above will now work.

implicit def Some[T](x: T) = Option(x)
user foreach { u => println(u.name) }

Taking this a step further, we can map the user name and print it like this. If the user is null, the name is never mapped and never printed.

user map { _.name } foreach println

Nulls be gone! 🙂

Now of course there are many cases in Java where nulls are legitimate values, and for this reason Scala supports Some(null) = null. But as shown above, that doesn’t mean we can’t override that with Some(null) = None.

Advertisements

Written by warpedjavaguy

June 8, 2011 at 4:35 am

Posted in java, scala

%d bloggers like this: