Emulating Haskell's Maybe in Java with Checked Exceptions

July 17, 2009

Haskell is one of the interesting languages that I've been looking into off and on for a while now. I think the type system is really interesting and if you don't know Haskell, you should check it out. I've found that the best introduction to the language is Learn You A Haskell. Once you've got the basics down, you should tackle Real World Haskell, which is a much more in-depth book. I'm still working through it when I have some spare time.

One interesting concept in Haskell is the Maybe type. The purpose of the Maybe type is to declare in the type signature of a function that a function might return a value or it might return nothing. In Java, the return type of method can't indicate that. For example, if you have a method with this signature:

Object lookup(String key)

You have no idea if that will actually give you an object or not. The lookup method may or may not be implemented in such a way that prevents it from returning null. If the method does return null, you are risking the possibility of encountering the dreaded NullPointerException.

Haskell doesn't have this problem because it doesn't treat null in the same way that Java treats null. As we just implied in the previous paragraph, this is a valid implementation of the lookup method from the Java compiler's perspective:

Object lookup(String key) {
  return null;
}

Haskell's equivalent to null is called Nothing, but the difference is that Nothing cannot stand in for all other types, as it can in Java. So if you actually had a type called Object in Haskell, a function that returns an Object cannot return Nothing.

A practical example of this would be looking up a value in a Map. When you try to retrieve the value for a key in a map, you might get a value or you might not find one. Here is an invalid Haskell program that tries to do that:

import Prelude hiding (lookup)
import Data.Map

zipToCityMap = fromList([("21230", "Baltimore"), ("21046", "Columbia")])

main = do
  putStrLn "What is your zip code?"
  zip <- getLine
  putStrLn $ "You live in " ++ (lookup zip zipToCityMap)

If you try to run this (which you can do on a mac by first installing ghc through macports with the command sudo port install ghc, then saving the contents above to city.hs and running the command runghc city.hs), you will get this compilation error:

city.hs:9:32:
    Couldn't match expected type `[Char]'
           against inferred type `Maybe [Char]'
    In the second argument of `(++)', namely
        `(lookup zip zipToCityMap)'
    In the second argument of `($)', namely
        `"You live in " ++ (lookup zip zipToCityMap)'
    In the expression:
          putStrLn $ "You live in " ++ (lookup zip zipToCityMap)

What the compiler is trying to say is that the lookup function returns a Maybe [Char], not a [Char]. [Char] is a type that is a list of characters. In Haskell, the type String is just an alias for [Char]. By wrapping the [Char] in a Maybe, the lookup function requires it's callers to unravel the value out of the Maybe in order to use it as well as explicitly handle the case that no value was found. If we modify our program to look like this:

import Prelude hiding (lookup)
import Data.Map

zipToCityMap = fromList([("21230", "Baltimore"), ("21046", "Columbia")])

main = do
  putStrLn "What is your zip code?"
  zip <- getLine
  case lookup zip zipToCityMap of
    Nothing -> putStrLn "I don't know where you live"
    Just city -> putStrLn $ "You live in " ++ city

This will compile and run. The program will ask you for your zip code and it will print the city you live in, but if it can't find it in its "database", it prints a friendly response rather than blowing up. Now here's a Java version of this program:

import java.util.Scanner;
import java.util.Map;
import java.util.HashMap;

class City {

  String name;
  String state;

  public City(String name, String state) {
    this.name = name;
    this.state = state;
  }

  public static City lookup(String zip) {
    Map zipToCityMap = new HashMap();
    zipToCityMap.put("21230", new City("Baltimore", "MD"));
    zipToCityMap.put("21046", new City("Columbia", "MD"));
    return (City)zipToCityMap.get(zip);
  }

  public static void main(String[] args) {
    System.out.println("What is your zip code?");
    Scanner in = new Scanner(System.in);
    String zip = in.nextLine();
    System.out.println("You live in "+lookup(zip).name);
  }

}

If you compile and run this, if you enter one of the chosen zip codes, it will tell you what city you live in. But if you enter an incorrect zip code, you will get the dreaded NullPointerException:

$ javac City.java
Note: City.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ java City
What is your zip code?
21230
You live in Baltimore
$ java City
What is your zip code?
90210
Exception in thread "main" java.lang.NullPointerException
        at City.main(City.java:26)

Now to get around this you would normally just check to see if the city is null and then print an alternative method if it is:

City city = lookup(zip);
if(city != null) {
  System.out.println("You live in "+.name);  
} else {
  System.out.println("I don't know where you live");
}

We know to do this only because we know the lookup method might return null. It would be nice if when defining the lookup method we could make it explicit to the callers of the method that the method might return null and have the compiler generate an error if the caller does not explicit check for the condition of a null result. If this were the case, a NullPointerException would be impossible. This is exactly what Haskell's Maybe type does.

A few other people have attempted to define a class in Java that mimics the Haskell Maybe type. That appears to work somewhat, but actually seems fairly convoluted to me. Another way to achieve the same goal would be to use a checked exception. To do this, put this in a file called MaybeNullException:

public class MaybeNullException extends Exception {}

Then add throws MaybeNullException to the end of the method signature for the lookup method. With just those changes, you will get a compilation error:

City.java:26: unreported exception MaybeNullException; must be caught or declared to be thrown
    System.out.println("You live in "+lookup(zip).name);

Which is exactly what we want. We have indicated in the method signature of our lookup method that it might return null, therefore the caller of our method is required to do something about it.

To get this to work, first we need to make the lookup method actually throw the exception if the city isn't found. This is semantically equivalent to returning Nothing in the Haskell version:

City city = (City)zipToCityMap.get(zip);
if(city == null) {
  throw new MaybeNullException();
} else {
  return city;
}

And then in the method where we call lookup, we need to check for the Exception:

try {
  System.out.println("You live in "+lookup(zip).name);  
} catch(MaybeNullException e) {
  System.out.println("I don't know where you live");  
}

Now we get the desired behavior:

$ javac -cp . City.java
Note: City.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ java City
What is your zip code?
21230
You live in Baltimore
$ java City
What is your zip code?
90210
I don't know where you live

The problem with this solution, at least from a Java perspective, is that many people, myself included, consider checked exceptions to be a failed experiment. Making use of checked exceptions all over your code results in having to add a lot more boilerplate and try/catch statements. But is this a small price to pay for eliminating the NullPointerException? Or does the Maybe type impose too much overhead in Haskell, making it doomed to fall out of favor the same way checked exceptions did in Java? My thought is no, that packing as much information as possible into the type of functions is the essence of Haskell. Also in Java, checked exceptions are something programmers learned to essentially get rid of by wrapping them in unchecked exceptions, where Nothing is very core to the way Haskell works. Nevertheless I thought this was an interesting thought experiment and might serve as a good analogy for Java programmers learning Haskell.

I should point out one more thing on this topic which is that the Maybe type is definitely more powerful in Haskell because it is a Monad. Because of this you can chain function calls together without having to deal with the possibility of something like a null pointer exception. The Java Checked Exception doesn't provide us a similar mechanism.

Posted in Technology | Tags NullPointerException, Maybe, Haskell, Java, MaybeMonad, CheckedExceptions | 18 Comments