DEV Community

Sergiy Yevtushenko
Sergiy Yevtushenko

Posted on • Updated on

Beautiful World of Monads

Let me start with disclaimer. Explanation below in no way pretends to be precise or absolutely accurate from the point of view of Functional Programming. Instead I’m focusing on clarity and simplicity of the explanation to let as many Java developers get into this beautiful world.

When I started digging into Functional Programming few years ago, I’ve quickly discovered that there are an overwhelming amount of information, but very little of it is understandable to average Java developer with almost exclusively imperative background. These days situation is slowly changing. There are a lot of articles which explain, for example, basic FP concepts and how they are applicable to Java. Or articles explaining how to use Java streams properly. But Monads still remain out of the focus of these articles. I don’t know why this happens, but I’ll try to fill this gap.

What Is Monad, Anyway?

The Monad is … a design pattern. As simple as that. This design pattern consists of two parts:

  • Monad is a container for some value. For every Monad there are methods which allow wrap value into Monad.
  • Monad implements “Inversion of Control” for value contained inside. To achieve this Monad provides methods which accept functions. These functions take value of the same type as stored in Monad and return transformed value. The transformed value is wrapped into the same kind of Monad as source one.

To understand second part of the pattern it will be convenient to look at the imaginable Monad interface:

interface Monad<T> {
    <R> Monad<R> map(Function<T, R> mapper);
    <R> Monad<R> flatMap(Function<T, Monad<R>> mapper);
}
Enter fullscreen mode Exit fullscreen mode

Of course particular Monad usually has far more rich interface, but these two methods definitely should be present.

At first look accepting functions instead of giving access to value is not a big difference. In fact this enables Monad to retain full control on how and when to apply the transformation function. When you call getter you expect to get value immediately. In case of Monad transformation can be applied immediately or not applied at all or it’s application can be delayed. Lack of direct access to value inside enables monad to represent value which is even not yet available!

Below I’ll show some examples of Monads and which problems they can address.

The Story of The Missing Value or Option/Maybe Monad

This Monad has many names — Maybe, Option, Optional. Last one sounds familiar, isn’t it? Well, since Java 8 Optional is a part of the Java Platform.

Unfortunately Java Optional implementation makes too much reverences to traditional imperative approaches and this makes it less useful than it can be. In particular Optional allows application to get value using .get() method. And even throw NPE if value is missing. As a consequence Optional usage is often limited to represent return potentially missing value, although this is only small part of the potential usages.

The purpose of the Maybe Monad is to represent value which potentially can be missing. Traditionally this role in Java is reserved for null. Unfortunately this causes a lot of various issues, including famous NullPointerException.
If you expect that, for example, some parameter or some return value can be null, you ought to check it before use:

    public UserProfileResponse getUserProfileHandler(final User.Id userId) {
        final User user = userService.findById(userId);
        if (user == null) {
            return UserProfileResponse.error(USER_NOT_FOUND);
        }

        final UserProfileDetails details = userProfileService.findById(userId);

        if (details == null) {
            return UserProfileResponse.of(user, UserProfileDetails.defaultDetails());
        }

        return UserProfileResponse.of(user, details);
    }
Enter fullscreen mode Exit fullscreen mode

Looks familiar? Sure it does.
Lets take a look how Option Monad changes this (with one static import for brevity):

    public UserProfileResponse getUserProfileHandler(final User.Id userId) {
        return ofNullable(userService.findById(userId))
                       .map(user -> UserProfileResponse.of(user,
                                                           ofNullable(userProfileService.findById(userId))
                                                                   .orElseGet(UserProfileDetails::defaultDetails)))
                       .orElseGet(() -> UserProfileResponse.error(USER_NOT_FOUND));
    }
Enter fullscreen mode Exit fullscreen mode

Note that code is much more concise and contains much less “distraction” from business logic.

This example shows how convenient monadic “inversion of control”: transformations don’t need to check for null, they will be called only when value is actually available.

The “do something if/when value is available” is a key mindset to start conveniently using Monads.

Note that example above retains original API’s intact. But it makes sense to use the approach wider and change API’s so they will return Optional instead of null:

    public Optional<UserProfileResponse> getUserProfileHandler4(final User.Id userId) {
        return optionalUserService.findById(userId)
                                  .flatMap(user -> userProfileService.findById(userId)
                                                                     .map(profile -> UserProfileResponse.of(user, profile)));
    }
Enter fullscreen mode Exit fullscreen mode

Few observations:

  • The code even more concise and contains nearly zero boilerplate
  • All types are automatically derived. This is not always so, but in vast majority of cases types are derived by compiler despite weaker type inference in Java comparing to, for example, Scala
  • There are no explicit error processing, instead we can focus on “happy day scenario”.
  • All transformations are composing and chaining conveniently, no breaks or distractions from main business logic.

In fact properties above are common for all Monads.

To Throw Or Not To Throw That Is The Question

Things not always going as we would like and our applications living in real world, full of suffering, errors and mistakes. Sometimes we can something with them, sometimes not. If we can’t do anything we would like at least notify caller that things went not as we anticipated.

In Java we traditionally have two mechanisms to notify caller about a problem:

  • Return special value (usually null)
  • Throw an exception

Instead of returning null we can also return Option Monad (see above), but often this is not enough as more detailed information about error is necessary. Usually we throw an exception in this case.
There is a problem with this approach though. Actually even few problems.

  • Exceptions break execution flow
  • Exceptions add a lot of mental overhead

Mental overhead caused by exceptions depends on types of exceptions:

  • Checked exceptions forcing you either to take care of them right here or declare them in signature and shift headache to the caller
  • Unchecked exceptions cause the same level of issues but you don’t have support from compiler

Don’t know which one is worse.

Here Comes Either Monad

Let’s analyse issue for the moment. What we want to return is a some special value which can be exactly one of two possible things: result value (in case of success) or error (in case of failure). Note that these things are mutually exclusive — if we return value there is no need to carry error and vice versa.

Above is almost exact description of Either Monad: any given instance contains exactly one value and this value has one of two possible types.

The interface of Either Monad can be described like this:

interface Either<L, R> {
    <T> Either<T, R> mapLeft(Function<L, T> mapper);
    <T> Either<T, R> flatMapLeft(Function<L, Either<T, R>> mapper);
    <T> Either<L, T> mapLeft(Function<T, R> mapper);
    <T> Either<L, T> flatMapLeft(Function<R, Either<L, T>> mapper);
}
Enter fullscreen mode Exit fullscreen mode

The interface is rather verbose as it’s symmetric in regard to left and right values. For the narrower use case when we need to deliver success or error it means that we need to agree on some convention — which type (first or second) will hold error and which will hold value.
Symmetric nature of Either makes it more error prone in this case as it’s easy to unintentionally swap error and success values in code.
While most likely this problem will be caught by compiler, it’s better to tailor Either for this particular use case. This can be done if we fix one of the types. Obviously it’s more convenient to fix error type as Java programmers are already used to having all errors and exceptions derived from single Throwable type.

Result Monad — Either Monad Specialized for Error Handling & Propagation

So, let’s assume that all errors implement same interface and let’s call it Failure. Now we can simplify and reduce interface:

interface Result<T> {
    <R> Result<R> map(Function<T, R> mapper);
    <R> Result<R> flatMap(Function<T, Result<R>> mapper);
}
Enter fullscreen mode Exit fullscreen mode

The Result Monad API looks very similar to API of Maybe Monad.

Using this Monad we can rewrite previous example:

    public Result<UserProfileResponse> getUserProfileHandler(final User.Id userId) {
        return resultUserService.findById(userId)
                                .flatMap(user -> resultUserProfileService.findById(userId)
                                                                         .map(profile -> UserProfileResponse.of(user, profile)));
    }
Enter fullscreen mode Exit fullscreen mode

Well, it’s basically identical to example above, the only change is kind of Monad — Result instead of Optional. Unlike previous example here we have full information about error, so we can do something with that at upper level. But still, despite full error handling code remains simple and focused on the business logic.

“Promise is a big word. It either makes something or breaks something.”

Next Monad I’d like to show will be the Promise Monad.

Must admit that I’ve not found authoritative answer if Promise is a monad or not. Different authors have different opinion in regard to it. I’m looking at it from purely pragmatic point of view: it looks and behaves a lot like other monads, so I consider them a monad.

The Promise Monad represents a (potentially not yet available) value. In some sense it’s very similar to Maybe Monad.

The Promise Monad can be used to represent, for example, result of request to external service or database, file read or write, etc. Basically it can represent anything which requires I/O and time to perform it. The Promise supports the same mindset as we’ve observed with other Monads — “do something if/when value is available”.

Note that since it’s impossible to predict if operation will be successful or not, it’s convenient to make Promise represent not value itself but Result with value inside.

To see how it works, lets take a look example below:

...
public interface ArticleService {
    // Returns list of articles for specified topics posted by specified users
    Promise<Collection<Article>> userFeed(final Collection<Topic.Id> topics, final Collection<User.Id> users);
}
...
public interface TopicService {
    // Returns list of topics created by user
    Promise<Collection<Topic>> topicsByUser(final User.Id userId, final Order order);
}
...
public class UserTopicHandler {
    private final ArticleService articleService;
    private final TopicService topicService;

    public UserTopicHandler(final ArticleService articleService, final TopicService topicService) {
        this.articleService = articleService;
        this.topicService = topicService;
    }

    public Promise<Collection<Article>> userTopicHandler(final User.Id userId) {
        return topicService.topicsByUser(userId, Order.ANY)
                           .flatMap(topicsList -> articleService.articlesByUserTopics(userId, topicsList.map(Topic::id)));
    }
}
Enter fullscreen mode Exit fullscreen mode

To bring whole context I’ve included both necessary interfaces, but actually interesting part is the userTopicHandler() method. Despite suspicious simplicity this method does following:

  • Calls TopicService and retrieve list of topics created by provided user
  • When list of topics is successfully obtained, the method extracts topic ID’s and then calls ArticleService and retrieves list of articles created by user for specified topics
  • Performs end-to-end error handling

Afterword

The Monads are extremely powerful and convenient tool. Writing code using “do when value is available” mindset requires some time to get used to, but once you start getting it, it will allow you to simplify your life a lot. It allows to offload a lot of mental overhead to compiler and make many errors impossible or detectable at compile time rather than at run time.

Top comments (4)

Collapse
 
klvenky profile image
Venkatesh KL

So very true. I think we can do a lot with this approach.
That's inspiring. Thanks.
Javascript added a similar feature known as "Optional Chaining". This had reduced a lot of repetitive code which we used to write. 100% relatable.
Cheers

Collapse
 
phouchens profile image
Perry H

Nice write up. Thanks for sharing.

Collapse
 
skanagavelu profile image
Kanagavelu Sugumar

Thank you!, Helpful! are Promise and Result available in java?

Collapse
 
siy profile image
Sergiy Yevtushenko

Here you can find link to library which implements Result and Option. Here exist one of possible implementations of Promise.