DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • What Is Applicative? Basic Theory for Java Developers
  • Redefining Java Object Equality
  • Java vs. Scala: Comparative Analysis for Backend Development in Fintech
  • Singleton: 6 Ways To Write and Use in Java Programming

Trending

  • Teradata Performance and Skew Prevention Tips
  • The Role of Functional Programming in Modern Software Development
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • Customer 360: Fraud Detection in Fintech With PySpark and ML
  1. DZone
  2. Coding
  3. Java
  4. What Is a Monad? Basic Theory for a Java Developer

What Is a Monad? Basic Theory for a Java Developer

Are you a Java developer who wants to know the theory behind Monads? Here you will find a step-by-step tutorial that will help you understand them.

By 
Bartłomiej Żyliński user avatar
Bartłomiej Żyliński
DZone Core CORE ·
Updated Dec. 08, 21 · Tutorial
Likes (23)
Comment
Save
Tweet
Share
46.9K Views

Join the DZone community and get the full member experience.

Join For Free

A monad is a concept originating from a part of mathematics called category theory, not a class or trait. In this article, I will try to explain its structure and inner workings. With the use of Optional from Java, I will try to describe all of this in a more understandable way. I will also implement a basic monad to better understand how they work and conclude with a short usage example to show the monad’s advantage over the non-monad approach.

The source code for this article is available in GitHub repository.

Why Learn How Monads Work?

First of all, it is always good to have a basic understanding of how things that we use work. If you are a Java developer, you probably use monads and may even not know it. It may surprise you but two of the best-known Java 8 features, namely Stream and Optional are monad implementations. 

In addition, functional programming is becoming more and more popular nowadays, so it is possible that we will have more similar monadic structures. In such a case, knowing what a monad is and how it works may become even more valuable. 

If you also want to learn something more about Optional history I can recommend you another one of my texts in which I am describing changes in Optional API since its introduction.

Let’s start with describing what a monad is – more or less accurately. In my opinion, the matter here is fairly straightforward.

Monad is just a monoid in the category of endofunctors

Based on a quote from "Categories for the Working Mathematician" by Saunders Mac Lane.  

Back to being serious...

What Is Monad?

After reading the intro, you know that a monad is a concept from category theory. In the world of software, it can be implemented as a class or trait in any statically typed language with generics support. Moreover, we can view it as a wrapper that puts our value in some context and allows us to perform operations, specifically operations that return value wrapped in the same context, on the value. Also, we can chain the operations in such a manner that the output of an operation at any step is the input to the operation at the next step.

Examples of monads in modern-day programming languages:

  • Stream (Java)
  • Optional/Option (Java/Scala)
  • Either (Scala)
  • Try (Scala)
  • IO Monad (Haskell)

Monad Laws

The last thing that needs mentioning while speaking of monads is their laws. If we want to consider our implementation a real monad, we must obey them. There are three laws: Left Identity, Right Identity, and Associativity. In my opinion, it can be somewhat hard to understand what they actually mean.

Now, with the help of Referential Monad, I will try to explain the above laws in a more detailed way.

But first a few assumptions:

  • f is a function mapping from type T to type Optional<R>
Java
 
Function<Integer, ReferentialMonad<String>> f = i -> ReferentialMonad.of(i.toString());
  • g  is a function mapping from type R to type Optional<U>
Java
 
Function<String, ReferentialMonad<Long>> g = s -> ReferentialMonad.of(Long.valueOf(s));
  1. Left Identity

    If we create a new monad and bind it to the function, the result should be the same as applying the function to the value.

    Java
     
    ReferentialMonad<String> leftIdentity = ReferentialMonad.of(x).flatMap(f);
    ReferentialMonad<String> mappedX = f.apply(x);
    leftIdentity.equals(mappedX);
  2. Right Identity

    The result of binding a unit function to a monad should be the same as the creation of a new monad.

    Java
     
    ReferentialMonad<Integer> rightIdentity = ReferentialMonad.of(x).flatMap(ReferentialMonad::of);
    ReferentialMonad<Integer> wrappedX = ReferentialMonad.of(x);
    rightIdentity.equals(wrappedX);
  3. Associativity

    In the chain of function applications, it should not matter how functions are nested.

    Java
     
    ReferentialMonad<Long> leftSide = ReferentialMonad.of(x).flatMap(f).flatMap(g);
    ReferentialMonad<Long> rightSide = ReferentialMonad.of(x).flatMap(v -> f.apply(v).flatMap(g));
    leftSide.equals(rightSide);

If you are enjoying reading about Monads and want to learn similarly related concepts, I recommend you read my latest article about Functors.

Creation of Monad

Now, when we know the basics, we can focus on implementation.

The first thing we need is a parameterized type M<T>, which is a wrapper for our value of type T. Our type must implement two functions:

  • of (unit) which is used to wrap our value and has the following signature M<T>(T).
  • flatMap (bind) responsible for performing operations. Here we pass a function that operates on value in our context and returns it with another type already wrapped in context. This method should have the following signature M<U> (T -> M<U>).

To make it more understandable, I will use Optional one more time and show what the above structure looks like in this case.

Here, the first condition is met right away because Optional is a parameterized type. The role of the unit function is fulfilled by ofNullable and of methods. FlatMap plays the role of the bind function. Of course, in the case of Optional, type boundaries allow us to use more complex types than in the definition above.

Done With the Theory, Let’s Implement

Java
 
package org.pasksoftware.monad.example;

import java.util.function.Function;

public final class WrapperMonad<A> {

    private final A value;

    private WrapperMonad(A value) {
        this.value = value;
    }

    static <A> WrapperMonad<A> of(A value) {
        return new WrapperMonad<>(value);
    }

    <B> WrapperMonad<B> flatMap(Function<A, WrapperMonad<B>> f) {
        return f.apply(value);
    }

    // For sake of asserting in Example
    boolean valueEquals(A x) {
        return value.equals(x);
    }
}


Et voila, monad implemented. Let’s describe in detail what exactly I have done here.

What Exactly Happened Here

The base of our implementation is the parameterized class with the immutable field named "value", which is responsible for storing our value. Then, we have a private constructor, which makes it impossible to create an object in any other way than through our wrapping method - of.

Next, we have two basic monad functions, namely of (equivalent of unit) and flatMap (equivalent of bind), which will guarantee that our implementation fulfills the required conditions in the form of monad laws.

With the functionalities described, it is time for a usage example. So here it is.

Java
 
package org.pasksoftware.monad.example;

import java.util.function.Function;

public class Example {

    public static void main(String[] args) {
        int x = 2;

        // Task: performing operation, returning wrapped value, over the value inside the container object.

        // Non-Monad
        Function<Integer, Wrapper<String>> toString = i -> new Wrapper<>(i.toString());
        Function<String, Wrapper<Integer>> hashCode = str -> new Wrapper<>(str.hashCode());
        Wrapper<Integer> wrapper = new Wrapper<>(x);
        Wrapper<String> stringifyWrapper = toString.apply(wrapper.value);
        // One liner - Wrapper<Integer> hashCodedWrapper = hashCode.apply(toString.apply(x).value);
        Wrapper<Integer> hashCodedWrapper = hashCode.apply(stringifyWrapper.value);


        // Monad
        Function<Integer, WrapperMonad<String>> toStringM = i -> WrapperMonad.of(i.toString());
        Function<String, WrapperMonad<Integer>> hashCodeM = str -> WrapperMonad.of(str.hashCode());

        WrapperMonad<Integer> hashCodedWrapperMonadic = WrapperMonad.of(x)
                .flatMap(toStringM)
                .flatMap(hashCodeM);

        assert hashCodedWrapperMonadic.valueEquals(hashCodedWrapper.value);
        System.out.println("Values inside wrappers are equal");
    }
}


In the code above, apart from seeing how monads work, we can also see a few pros of using them.

In the monadic part, all operations are combined into a single execution pipeline which makes the code more declarative, and easier to read and understand. Additionally, if we decided someday to add error-handling logic, it can be nicely encapsulated inside of and flatMap methods.

On the other hand, in the non-monadic part of the example, we have a different design with package-private field value, we need a way to access value from outside the wrapper, which breaks the encapsulation. As for now, the snippet is readable enough but you may notice that it is not extension-friendly. Adding any kind of exception handling may make it quite spaghetti.

Summing up

Monad is a very useful and powerful concept and probably many of us use it in our day-to-day work. I tried to provide a clear and descriptive explanation of its theoretical basis and logic behind it. I implemented a custom monad to show that it is not a complex structure. In the example above, I showed what the use of Monad may look like, what the potential pros of such an approach are, and how it can differ from normal methods call. Thank you for your time.

If you enjoyed reading about Monads and want to read more about other similar concepts I can recommend you my article about Functors.

Frequently Asked Questions About Monad

What Is Monad?

A Monad is a concept originating from a part of mathematics called category theory.

Why Should I Care About Monad?

If you are a Java developer, you probably use Monad every day but I can tell you have questions. For example, Stream and Optional are Monad implementations that are the most confusing objects. Additionally, functional programming is becoming more and more popular, so we may expect more structures like this.

What Are Monad Laws?

Every Monad implementation has to satisfy three laws: Left Identity, Right Identity, and Associativity.

What Do I Need to Implement Monad?

To implement Monad, you need a parameterized type M<T> and two methods unit and bind.

Monad (functional programming) Java (programming language) Functional programming

Opinions expressed by DZone contributors are their own.

Related

  • What Is Applicative? Basic Theory for Java Developers
  • Redefining Java Object Equality
  • Java vs. Scala: Comparative Analysis for Backend Development in Fintech
  • Singleton: 6 Ways To Write and Use in Java Programming

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!