Java 8 Optional as a Monad

Datetime:2016-08-23 00:36:42          Topic: Java8           Share

Lately there has been a lot of discussions around functional programming and object oriented programming and their differences. A few functional programming constructs had also been introduced in Java 8 release couple of years back. Since then I have been exploring functional programming and I have realized that using a mix of both functional and object oriented styles in my code has enabled me to write code that is both easy to read and easy to explain.

In this article I will show the following:

  • How Java 8’s Optional can be used to write code in a functional way and
  • Understand Monads using Optional as an example and how it helps build robustness into the code

Code sample for this article can be found at this link . As a part of my exploration of functional programming, I have been reading Functional Programming in JavaScript , by Luis Atencio . I have found the text very useful for understanding the concepts in a practical way.

Example use case

Let’s try to understand the concepts using an example. Let’s say you have to write a program to find out the current location of an order based on it’s order id. While writing the program, the following conditions should be kept in mind:

  • To be able to tell the current location of an order, it should have a tracking id
  • If location information cannot be found then return “No location found”

To be able to write the implementation we will be using the following domain models and services.

class Order {
    private final String trackingId;
    Order(String trackingId) { this.trackingId = trackingId;}
    boolean hasTrackingId() { return trackingId != null; }
    String getTrackingId() { return trackingId; }
}

class TrackingInfo {
    private final String currentLocation;
    TrackingInfo(String currentLocation) { this.currentLocation = currentLocation; }
    String getCurrentLocation() { return currentLocation; }
}

interface OrderService {
    Order findOrderByOrderId(String orderId);
}

interface ShippingService {
    TrackingInfo findTrackingInfoByTrackingId(String trackingId);
}

An imperative implementation

By imperative , I mean a style of writing programs generally used when we write programs using object oriented principles. The following points are worth noting for code implementation shown below:

  • There can be many situations that can break the program in runtime. For the purpose of this article we are just focussing on nullability of values.
  • By guarding against the null values you can make your program robust.
  • Building robustness in the code has led to introduction of if-else blocks which reduces the readability of code.
String getCurrentLocationOfOrderWithId(String orderId) {
    if (orderId != null) {
        Order order = orderService.findOrderByOrderId(orderId);

        if (order != null && order.hasTrackingId()) {
            TrackingInfo trackingInfo =
                shippingService.findTrackingInfoByTrackingId(order.getTrackingId());

            if (trackingInfo != null) {
                return trackingInfo.getCurrentLocation();
            }
        }
    }

    return "No location found";
}

A declarative implementation

Now let’s rewrite the above program using a declarative style using Java 8’s Optional . By declarative , I mean a style of writing programs generally used when we write programs using functional programming principles.

The following points are worth noting for the code implementation shown below:

  • Though the null checks cannot be seen in the code below, the code is as robust as it was before.
  • This is because the null checks have been taken care of by the implementation of Optional .
  • The implementation has been written as a series of computation steps that, I hope, is easy for you to comprehend.
  • None of the interfaces and domain objects had to be changed to be able to write the below code.
String monadicGetCurrentLocationOfOrderWithId(String orderId) {
    return Optional.ofNullable(orderId)
        .map(orderService::findOrderByOrderId)
        .filter(Order::hasTrackingId)
        .map(Order::getTrackingId)
        .map(shippingService::findTrackingInfoByTrackingId)
        .map(TrackingInfo::getCurrentLocation)
        .orElse("No location found");
}

Understanding Monads

By definition a Monad is a structure that wraps a value and does the following two things:

  • Provide methods or constructs to build programs as a series or pipeline of computations.
  • Decorate each computation with additional processing rules provided by the Monad.

Let’s compare this definition to Java 8’s Optional introduced above which is also a Monad.

  • map , filter and flatMap (not shown above) are the methods on Optional that uses method chaining to build series or pipeline of computations.
  • The computation as a part of each of these methods is decorated with a null value check functionality, executing the computation only if the value contained within it is not null.

Understanding Optional as a Monad

Let’s use the declarative implementation using Optionals, shown above, to explain Monad in a little more detail.

We can see from above that the different values orderId , order , trackingId and trackingInfo could have been null. But we wrapped or decorated each of them with a structure, Optional, which provided the null checking.

ofNullable returns an empty Optional if orderId is null. Otherwise it stores the id in an internal field of Optional and returns the Optional object containing the value. The below is taken from the Java source code for ofNullable .

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

The computations as part of arguments of methods map and filter on the Optional are performed only when the underlying value is not null i.e. when the Optional object is not empty. The below is taken from the Java source code for filter method. Note the predicate passed to filter is evaluated only when Optional is not empty i.e. value is present. Also the return type is again Optional so that further chaining can be done.

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

orElse returns the No location found only when the instance of Optional, upon which the method is called, is empty. The below is taken from the Java source code for orElse . If the values contained within is not null, then the actual value is passed back.

public T orElse(T other) {
    return value != null ? value : other;
}

We saw above the Optional is a monad that decorates each computation with a null value check. Similarly there are monads for decorating computations with other functionalities like error handling (try-catch), etc.

Summary

I hope I was able to show how introducing functional programming concepts into your code can increase the clarity and comprehension. Writing functional programs may not be immediately intuitive however with practice and learning the functional programming vocabulary you will be able to improve your code. Using Monads in your code help you in writing robust code while preserving the declarative coding style.





About List