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

Comments

1.

Another thing to note about why Maybe works well in Haskell is that type signatures in Haskell are inferred if you leave them out, so if you get tired of writing them, you can just let the compiler work it out (it still checks the inferred types).

Still, it's considered good practice to write type signatures for top-level things, and especially things which are exported from a module, but Maybe doesn't seem like much of a burden there. It's only 5 characters and expresses clearly the intent that there might be no value to be had.

# Posted By Cale Gibbard on Friday, July 17 2009 at 9:47 AM

2.

You say "the Maybe type is definitely more powerful in Haskell when you combine it other things such as the Maybe Monad", which is a little confusing. Maybe actually *is* a monad. The 'Maybe type' and the 'Maybe Monad' are the same thing. Remember, a monad is essentially just a type that wraps an inner type.

Unlike with Maybe in Haskell, using exceptions gives you no compile-time (type-checked) guarantee that the exception will be handled. This is, in my mind, the real power of Maybe.

Certainly, in other languages, dealing with 'Nothing' cases is often best achieved with exceptions rather than returning null, nil, None, et al.

If someone did want a 'null' value in Haskell that could essentially stand-in for any other type, they might want to explore the properties of 'undefined'. (":t undefined" in GHCI). This is really only useful for skeleton definitions.

# Posted By Mike Tomasello on Friday, July 17 2009 at 12:06 PM

3.

I'm not entirely sure how to put this, but there's a flaw running through your entire analysis. Namely, the "Maybe type" *is* the Maybe Monad. There is not difference between them, in spite of what you suggest in your last paragraph.

The whole idea of Monads is that you can wrap your functional code inside a type. That way, whenever you expect a side effect from your code, you can infer how to handle the side effect by the very nature of the type itself. Checked exceptions don't even come close to letting you do this.

A better analogy would be to imagine a utility class in which everything is static (to somewhat account for Haskell's immutability). Then you create your side-effect-producing class. Call it NullCheck or something like that, and make NullCheck the wrapper for your static class. In fact, let your static functionality be represented by a generic type in NullCheck, to allow NullCheck to hold any class whatsoever.

My head hurts just thinking about what this code would look like in Java, not to mention what it would take to get it to do anything useful.

I'm no expert on Monads. There are a lot of clever tricks with combining them that I haven't learned how to do yet. But even something as easy as the Maybe monad would be very tough to implement with Java as it is now. A bit easier to do in C/C++, mainly because of function pointers, but almost impossible to enforce.

# Posted By rtperson on Friday, July 17 2009 at 12:11 PM

4.

Three cheers for Mike T. making the point I was trying to make, and doing it with about 3 times more clarity.

There's a great analogy to help you understand monads here: http://www.haskell.org/all_about_monads/html/analogy.html

# Posted By rtperson on Friday, July 17 2009 at 12:22 PM

5.

@Mike Tomasello,

> Certainly, in other languages, dealing with 'Nothing' cases is often best achieved with exceptions rather than returning null, nil, None, et al.

Dealing with some Nothing cases, but not all since otherwise one wouldn't need null references (and they're clearly pretty often used).

Anyway @op as others have said, the Maybe type and the Maybe monad are the same thing (more precisely, Maybe is an instance of the Monad typeclass: http://haskell.org/ghc/docs/latest/html/libraries/base/Data-Maybe.html#t%3AMaybe). The monadic part (which allows you to perform operations on boxed values -- whether or not there's actually one -- by lifting functions into Maybe) is pretty specific to Haskell, but the idea of disallowing null references and using a type for that isn't, several languages have it though it's usually called Option/None/Some rather than Maybe/Nothing/Just (ML dialects e.g. SML, OCaml, F# as well as Scala).

# Posted By Masklinn on Friday, July 17 2009 at 1:16 PM

6.

@Mike Tomasello

> Unlike with Maybe in Haskell, using exceptions gives you no compile-time (type-checked) guarantee that the exception will be handled. This is, in my mind, the real power of Maybe.

By using a checked exception, you do get a compile-time guarantee that the exception will be handled. That was the comparison that I was trying to make in this article. Until we put some sort of handling of the MaybeNullException into the calling method, the code would not compile. What am I missing?

# Posted By Paul Barry on Friday, July 17 2009 at 3:15 PM

7.

@Mike Tomasello/@rtperson,

Good point about Maybe being a Monad. I've updated the wording to make that more clear.

Related to this though, I'm not quite clear exactly how to use the monad to navigate through a data structure and avoid null pointer exceptions. I think that you can do something similar to the Null Object Pattern in groovy:

http://groovy.codehaus.org/Null+Object+Pattern

In Groovy, say you have a reference to a Person object p, which has a Job object that the job method returns, and that has a salary method which returns the person's salary. So if you try to do:

p.job.salary

If the person has no job, job will return null, so you will get a null pointer when you try to call salary on null. If you use what they call the "safe-dereference operator" in Groovy, you can do this:

p.job?.salary

Which will return null if the person has no job or if the person's job has no salary.

Can the Maybe Monad be used in a similar way?

# Posted By Paul Barry on Friday, July 17 2009 at 3:28 PM

8.

Paul:

Re: checked exceptions, you are correct; This feature of Java's exceptions went completely over my head. I should have read more carefully.

It does seem like these checked exceptions can offer the advantages of Maybe. On the other hand, it seems like the disadvantages of checked exceptions as described in the articles you linked to would also apply to Maybe.

# Posted By Mike Tomasello on Saturday, July 18 2009 at 1:59 AM

9.

Paul @ #7, yes, you can chain Maybes the same way. Say you have "job" and "salary" functions with these signatures, and "bob" is a person:

job :: Person -> Maybe Job
salary :: Job -> Maybe Salary
bob :: Person

Then you can the "job" function to get either Bob's job (if he has one) or Nothing (if he has no job). And then, you can use the ">>=" (monadic bind) operator to pass the output of "job" to "salary", if there is an output, but otherwise just return Nothing immediately:

bobsSalary :: Maybe Salary
bobsSalary = job bob >>= salary

Andrej Bauer has a good rant about the evils of using null pointers instead of something like Maybe:

http://math.andrej.com/2009/04/11/on-programming-language-design/

See the "undefined values" section.

# Posted By solrize on Saturday, July 18 2009 at 4:43 AM

Comments Disabled