Understanding the Optional Monad in Java 8
What are monads? Let’s start with a play about the smug answer:
Smug dev: Oh this is just a simple use of the IO monad.
Junior dev: What’s a monad?
Smug dev: Oh, well, monads are just monoids in the category of endofunctors.
Junior dev: [weeps softly, walks out of office to start a beet farm in Vermont and live the simple life]
Seemingly whenever monads come up there’s a lot of fancy terminology from category theory that gets thrown around. However the concepts of category theory are irrelevant to what winds up being a simple and well understood functional programming concept. It’s simply not that hard.
So what are they? Think of monads as an object that wraps a value and allows us to apply a set of transformations on that value and get it back out with all the transformations applied. That’s more or less it. We put a value in what’s called a monadic context, apply whatever functions we wish to it, and then grab our final value back out.
With that in mind its fairly simple, but it will become more obvious once we take a look at the some examples. Today we’ll look at one of the monads Java 8 introduces to get a feel for it.
How many times have you had to write code like this to avoid a NullPointerException?
Not only is this kind of code messy and hard to read (and is perhaps a Law of Demeter violation — but that’s a separate post) — but its dangerous. If we forget to check one of these fields for null we could wind up with a NPE. I’ve been bitten by it before and I’m sure all Java developers have.
Well let’s think about this in terms of what we know about monads. What if we had a monad that allowed us to wrap a potentially null value, perform some transformations on it for us if the value is not null. We could then just take the potentially null value, apply all our get operations to it and then pull out our value should one exist. That’s exactly what Optional does.
Here we just map a series of functions over our Optional<Employee> and grab our relevant values. The last line, orElseThrow is of particular interest because that’s where we unwrap and get our final value out, or throw an exception. Here we’re saying “grab me the value inside Optional, throwing an exception if we don’t have a value”. In my opinion this method is poorly named, it really should be getOrElseThrow to more accurately describe what it’s doing.
At its heart, the Optional monad embodies a concept similar to the null object pattern. That is instead of having a null value we encapsulate the behavior we desire of null in an object. In this case this is represented by Optional.empty().
Optional also gives us a lot of useful methods that allow chaining behavior. This gives it a lot of expressive power. For instance, let’s say we have some logic around getting an age appropriate drink for an employee. If they’re greater than 21, they get their favorite beer if they have one. If not, they just get a Sprite.
There’s three method calls here, filter, flatMap and orElse. Let’s take a look at what they each do:
filter(employee -> employee.getAge() >= 21)
Returns another Optional<Employee>. In this case, if our employee is less than 21, we’ll be returned Optional.empty(), otherwise, we’ll get the original employee.
flatMap(employee -> employee.getFavoriteBeer())
If our optional is empty map will just give us another empty Optional, otherwise we’ll grab the employee’s favorite beer.
This is where we’re extracting our value, if we have a value inside our Optional at this time we’ll grab that value which will be a beer and would be equivalent to calling Optional.get(). If however we have an empty optional, we’ll only get a Sprite.
As you can see we’ve condensed our logic into one simple line of code. The power of Optional when used properly has the ability to reduce a lot of clutter inside your code by expressing your logic as filters and transformations on your data.
What makes a monad a monad?
To get more technical, for something to truly be a monad it must obey the three monad laws. There are two operations we need to define to talk about these laws, commonly called in functional programming circles as return and bind. Return is the operation that takes a value and wraps it in a monadic context. In the example of Optional this corresponds to the static factory method Optional.of. Bind is the operation that binds a function that produces a monad to our monad — in the instance of Optional this is equivalent to the flatMap method.
Let’s take a look at these laws, and see how Optional obeys them with a series of equality checks:
Left Identity: If we put a value in a monad and bind a function to it, its the same as just applying the function to a value:
This is obviously important as it defines how we expect our bind method to apply the functions we give it.
Right Identity: If we have a monad and bind that monad’s return method — it is the same as the original value:
Associativity: If we have a series of functions applied to a monad it doesn’t matter how they’re nested:
Associativity is powerful in that it allows us to nest our binds in a nice fashion. Instead of just doing mindless binds we can nicely nest them inside one another and make our code even nicer to read.
Taking it all in
This is really just scratching the surface of what is a much larger topic than the scope of this article. If you feel compelled to know more about the full power of monads checking out languages such as Haskell might be what you want to do as monads are pervasive in the language. Interestingly, IO, exceptions and many other things are handled by monads in Haskell. For further reading Learn You a Haskell for Great Good is an excellent resource.
However, I think for most Java developers just knowing of the expressive power that monads and Optional in particular can bring to your code goes a long way. Delving deep into topics isn’t for everyone, and in a lot of cases is completely superfluous to being productive with a tool. At the end of the day the Optional monad is just a tool to help you solve business problems more succinctly.