Maybe for Java and Guava

In Growing Object-Oriented Software, Guided by Tests we wrote, almost as an aside, that one of our design rules is:

Never pass null references between objects

We showed an example of replacing a potentially null value with a Maybe type that represents a value that may or may not exist. The example did not show the Maybe type in any detail, and since then people have asked for a more complete example that shows how such a type would be used in practice.

I've recently published an example implementation of a Maybe type for Java on Github in the maybe-java repository.

Avoiding Null References

The aim of the Maybe type is to avoid using 'null' references and, therefore, unchecked NullPointerExceptions. In a large project, null references are extremely problematic. Stray null references cause unchecked exceptions in code far away from the source of the reference. If programmers are never sure whether references are null or not, they bloat the code with null checks. And, if a reference is null, what should they do? They may throw an unchecked exception early, which is only slightly better than dereferencing a null pointer a bit later in the method. Or they may fall back to some default value, which is, in my experience, much worse because the actual runtime behaviour of the code becomes very difficult to understand.

Life is much easier if everyone on the team knows that parameters and returned values are never null. Nulls might be used inside an object as a sentinel value, but they never leak through an object's encapsulation boundary.

In Tell, Don't Ask style code, it's easy to avoid nulls. An object can just not send a message to collaborators about something does not exist, rather than pass a null reference as a parameter.

In functional code it's harder to avoid because we need to return values from functions and store them in data structures. What to do if a value does not exist if we don't represent that nonexistence as null? That's where the Maybe type comes in handy.

The Maybe Type

A Maybe<T> represents a possibly nonexistent value of type T. The Maybe type uses the Null Object pattern to hide whether the value does or does not exist and makes it impossible (without deliberate effort to circumvent the API) to try to use the value when it does not exist.

In the case of the example I published on Github, the nonexistent value represents the case when we do not know the value, rather than definitely know there is no value. This affects the implementation of equality, which is defined like that of SQL NULLs. I can imagine a need for a "known to be nothing" value, so feel free to fork the code and adapt it to your own needs.

A Maybe<T> is either unknown(), in which case a known value does not exist, or definitely(v), in which case the value is known to be v.

A Maybe<T> is iterable, which means you can use the for statement to extract a value and do something with it only if there is a value.

class Customer {
    public Maybe<String> emailAddress() { ... }
    ...
}

...

for (String emailAddress : aCustomer.emailAddress()) {
    sendEmailTo(emailAddress);
}

Maybe<T> being iterable really comes into its own when combined with the Guava library (previously google-collections), which has useful functions for working with collections in bulk. You can then program in terms of entire collections of things that might or might not exist, without having to test for the existence of each one.

For example, if you have a collection of 'maybe' email addresses, some of which might exist and some might not:

Iterable<Maybe<String>> maybeEmailAddresses = ...

I can get a set of only the actual email addresses in a single expression:

Set<String> actualEmailAddresses = newHashSet(concat(maybeEmailAddresses));

The newHashSet and concat functions are defined by Guava. Concat creates an Iterable<T> from an Iterable<Iterable<T>>, concatenating the elements of each sequence into a single sequence. Because unknown() is an empty iterable, the concatenated iterable only returns the definite values.

More likely, I have an iterable collection of Customers. Using a Function, I can write a single expression to get the email addresses of all customers who have an email address:

Here's a function to map a customer to its 'maybe' email address:

Function<Customer,Maybe<String>> toEmailAddress = new Function<Customer, Maybe<String>>() {
    public Maybe<String> apply(Customer c) { return c.emailAddress(); }
};

And here's how to use it to get all the email addresses that my customers have, so I can send them product announcements:

Set<String> emailAddresses = newHashSet(concat(transform(customers, toEmailAddress)));

If I just want to send emails, I don't need the hash set:

for (String emailAddress : concat(transform(customers, toEmailAddress))) {
    sendEmailTo(emailAddress);
}

The name "concat" is not very intention-revealing, so I'd probably define an alias named, for example, "known", to make the code more readable at the point of use. And the Function definition is a bit clunky, but Java doesn't yet (perhaps will never) have a clean syntax for referring to existing functions.

Maybe also has useful methods to work with individual values. For example, the otherwise method:

T otherwise(T defaultValue);

will return the Maybe's value if it is known and the defaultValue if it is not. E.g.

assertThat(unknown().otherwise(""), equalTo(""));
assertThat(definitely("foo").otherwise(""), equalTo("foo"));

Otherwise is overloaded to take a Maybe<T> as a default:

Maybe<T> otherwise(Maybe<T> maybeDefaultValue);

which lets you chain otherwise expressions:

assertThat(unknown().otherwise(definitely("X")).otherwise(""), equalTo("X"));

Maybe also has a method that uses a function to map a Maybe<T> to a Maybe<U>:

<U> Maybe<U> to(Function<T,U> mapping);

which transforms unknown() to unknown(), otherwise applies the function to the definite value and return the result wrapped in a Maybe.

Similarly there is a query method that takes a Predicate<T> and maps a Maybe<T> to a Maybe<Boolean>.

All of which features make it impossible (unless you really try) to write code that uses a value that doesn't exist the value of nothing and helps avoid NullPointerExceptions.

Copyright © 2010 Nat Pryce. Posted 2010-07-23. Share it.

Comments powered by Disqus