Monads explained in Kotlin

Overview

Monads explained in Kotlin

Monads, I don't know if you heard about them, if not please keep reading, if yes, please don't give up and keep reading as well.

The term 'monad' itself is weird but if you google it is even worse, you will be overflowed by a lot of cryptic blogs of functional programming trying to explain them or just being arrogant to show how smart-asses they are and how stupid you are.

Well, this is just another one, the difference, I am not a functional expert, just a simple engineer trying to make the suffering less painful when it comes to monads.

Why I am doing this? During my career I've been always trying to keep-up to date but also learn new things, and one of them was functional programming, and trust me, when you try to learn it there is a point that you face this scary term, yes, monads. First reaction? Cool a new thing with a cool name... (after some minutes) ... mmmh, this is abstract ... (after some hours) ... this is really hard ... (after weeks) ... I think I am getting it ... (after months) ... Ok, let's start over.

I don't know if you have had the same experience, but to me, it's been challenging in two ways:

  1. Understand what a monad is
  2. Explain what a Monad is

TBH, I think I will never be successful in any of them, but I least I will try 🤘 .

Having said that, are you interested?

You got my attention! I want to learn more!

Note: Maybe some the assumptions are not totally accurate from either mathematical or functional perspective, but, again, I am just trying to explain this useful pattern for non-functional-experts.

First things first: Some theory

Let's refresh a couple of concepts, because it is always good to keep in mind and reinforce base concepts before go in details with a more complex topic like Monads.

What is a function?

In mathematics a function is a relationship between two sets of data, where each element of one corresponds to one element of the other. Based in that definition, in programming a function is a relationship between two types, since a data type is a set of values.

What is FP?

Is a functional programming paradigm where programs are made composing functions.

Functional Programming is based on λ-calculus (compose and transform) instead of Imperative Programming, which is based in Von Neumann (mutate state).

Related concepts:

  • Functions are first class citizens
  • High Order Functions
  • Pure functions and Referential Transparency
  • Recursion
  • Non-strict Evaluation
  • Algebraic Data Types
  • Immutability
  • Side effects
  • Declarative Programming
  • Currying and Partial Application
  • Monads

FP adds a different way of thinking about problem-solving.

I had enough, please, explain the topic

One thing more before jump into monads: Let's use a real and practical problem to guide us through.

The Bank Account

Let's pick up a real and easy problem that will help us to explain the concept along the exercise in a practical way.

We are going to use the simplest bank account implementation that we can use for the sake of explaining the monads (a real bank account would be far more complex)

data class Account(val balance: BigDecimal)

What is the challenge here?

Operations like create an account, deposit or withdraw are susceptible to fail, let's see how we can deal with them with monads.

Why not just raise exceptions?

There is a lot of discussions and strong opinions around this topic over the net, to me is really easy, given an error:

  • Is it a crash? Then let it crash, let the exception fly, they are exceptional cases. Errors that you don't create/control, that come from other libs, frameworks, external resources, deal with them outside your domain, at the boundary of you program.
  • Is it an error that you control? Then use another error handling mechanism.

Why would be bad to use exceptions for my own errors?

  • In almost all the languages exceptions hide non-happy case flows, you can not type them in the function signatures, therefore, from client perspective you can not even notice and deal properly with them.
  • Using exceptions you are not forced to face your own errors, you can easily let it crash, delegating the control of the flow to someone else.
  • They can be expensive in terms of performance.

Yeah, sure, you will need to convince me

What is a monad?

A monad is a functional design pattern

Design pattern: Reusable solution to a commonly occurring problem within a given context in software design. In OOP we have the famous GOF - Gang of Four patterns (Singleton, Adapter, Decorator, Strategy, Observer, Visitor...).

A monad is a functional design pattern that solves recurrent problems such as:

  • Nullability - Maybe/Option monad
  • Error Handling - Either monad
  • DI (Dependency Injection) - Reader monad
  • Logging - Writer monad
  • Side Effects - IO monad
  • State handling - State monad
  • Iterable - List monad
  • Many others ...

Either Monad

For our exercise we are going to use one of the most useful ones, the Either monad (can have different names, for instance F# has Result type, in Golang it's a part of the syntax when function is obliged to return 2 results, etc.), to handle our errors:

  • Either type represents values with two possibilities, either Left or Right

  • Convention dictates that Left is used for failure and Right is used for success.

  • Mnemonic: "right" also means "success/correct/good".

A monad is a type in a context

Monads work with a type in a context, where context is a generic container that holds a value:

  • Is a type that wraps another type/s.
  • Is parameterised
  • The context matters, is semantic, gives some form of quality to the underlying type.

And here a very basic implementation:

sealed class Either<out A, out B> {
    data class Left<A>(val value: A) : Either<A, Nothing>()
    data class Right<B>(val value: B) : Either<Nothing, B>()
}

Bla bla bla, so boring, show me something real

Sure!

Let's create an account

In order to create an account we need to provide a way, we could use the constructor, but it does not allow us to return other types than the constructed one. Therefore, we can provide a factory method and make the constructor private, with this we can enforce the invariants of our account.

data class Account private constructor(val balance: BigDecimal) {

    companion object {
        fun create(initialBalance: BigDecimal): Either<NegativeAmount, Account> =
            if (initialBalance < 0) Either.Left(NegativeAmount)
            else Either.Right(Account(initialBalance))
    }
}

The code from consumer perspective looks like this:

Account.create(initialBalance: BigDecimal): Either<NegativeAmount, Account>

If we try to read it like a text, what is this signature telling us?

This creates an account with an initial balance, it can either fail because the amount was negative or success

Amazing 😍 , isn't it?

I still prefer my amazing code throwing exceptions

Are you sure? The same signature with exceptions:

Account.create(initialBalance: BigDecimal): Account

What can go wrong? Nothing, no signals of errors ... a consumer can happily use it without knowing that it can actually fail, this is a hidden flow.

I don't buy it, I can always:

  1. Add an amazing documentation.
  2. Check the implementation.
  3. Come back to java where I can use checked exceptions.

Well:

  1. Documentation: not clean, code should be self-explanatory, and also if you pass the function around as a lambda you will lose it.
  2. Check the implementation: Really? Not clean, a consumer should not know about the implementation details.
  3. Sure, come back to java where you can type it with throws NegativeAmountException, only one problem, since your method is throwing checked exceptions you can not pass the function around, bye bye High-Order-Functions. Maybe you can find a workaround to pass it, yeah, just a workaround.

Ok, fine, you got me

A monad can be mapped over.

What??? Easy, easy, calm down mathematicians and functional programming experts!!

Sadly, we can not explain monads without explain another pattern, the Functor.

Basically, a functor is a container that holds a value and allows us to map over it with one function called fmap or map:

Functor? fmap? Do I have an arrogant haskeller face?

Ok, ok, maybe you are more familiar with this:

listOf(1, 2).map { it + 1 }

You already have used the pattern in many languages and multiple times, mainly in collections when you want to map over all the elements and change them given a lambda.

Don't think in abstractions, supertypes or interfaces ... the implementation differs depending on the language, you can understand a functor as:

  • A parametric type
  • Has a ~map function to change the inner value type A passing a lambda (A -> B)

Possible simple implementation in kotlin using an interface:

interface Functor<out A> {
    fun <B> map(fn: (A) -> B) : Functor<B>
}

Interesting, I was using functors without knowing it ... 🤦‍ But why this is related to monads?

A Monad is also a functor and from client perspective, a monad without a map function is not the most useful construct.

Why I would need this with our Account?

Deposit money into the bank account

What if we want to deposit money to this account?

Easy! we just add a method on the account

data class Account private constructor(val balance: BigDecimal) {

    companion object {
        fun create(initialBalance: BigDecimal): Either<NegativeAmount, Account> =
            if (initialBalance < 0) Either.Left(NegativeAmount)
            else Either.Right(Account(initialBalance))
    }

    fun deposit(amount: BigDecimal): Account = this.copy(balance = this.balance + amount)
}

object NegativeAmount 

Now let's try to add money

 val account = Account.create(100.toBigDecimal())
when (account) {
    is Either.Right -> account.value.deposit(100.toBigDecimal())
    is Either.Left -> TODO() // now what?
}

Come on, this is worse than my beautiful-imperative code! What I have to do now? Throw an exception? And, what is all this boilerplate code?

Wait again, map to the rescue!

sealed class Either<out A, out B> {
    class Left<A>(val value: A) : Either<A, Nothing>()
    class Right<B>(val value: B) : Either<Nothing, B>()

    fun <C> map(fn: (B) -> C): Either<A, C> = when (this) {
        is Right -> Right(fn(this.value))
        is Left -> this
    }
}

And ...

val account = Account.create(100.toBigDecimal())
    .map { a -> a.deposit(100.toBigDecimal()) }

Boom! Cool, right?

But then, what if Account.create returns an error?

Our map is semantically attached to the type of the monad, in our case to the Either monad, it will only apply the fn if we have a Right, otherwise the function will be just ignored, our monad is right biased.

But, what if the amount is negative? What about errors?

Good! Kid, you are sharp, I'll give you that. The next section is going to fix that.

A Monad is a couple of functions

**Disclaimer: This is the most important part, pay attention.**

I am there!

Monads define two functions:

  • One to wrap a value in a monad (the container), called return or unit:

  • Another, known as bind or flatmap, to apply a function to the contained value that outputs another monad:

What the f****? Please, why I would even need these functions? explain this before I leave!

Take it easy, I am going to do it

Deposit money into the bank account, second try

Remember our function to deposit money without errors:

data class Account private constructor(val balance: BigDecimal) {
    // other stuff
    fun deposit(amount: BigDecimal): Account = this.copy(balance = this.balance + amount)
}

Let's add some errors here:

data class Account private constructor(val balance: BigDecimal) {
    companion object {
        fun create(initialBalance: BigDecimal): Either<NegativeAmount, Account> =
            applyAmount(initialBalance) { Account(it) }

        private fun applyAmount(amount: BigDecimal, fn: (BigDecimal) -> Account) =
            if (amount < ZERO) Either.Left(NegativeAmount)
            else Either.Right(fn(amount))
    }

    fun deposit(amount: BigDecimal): Either<NegativeAmount, Account> =
        applyAmount(amount) { this.copy(balance = this.balance + it) }
}

BTW, our Right and Left constructors could be considered as the unit function, they put a value in the Either context.

Now, it's time to deposit money again:

val account = Account.create(100.toBigDecimal())
    .map { a -> a.deposit(100.toBigDecimal()) }

And the tricky question. Could you tell me the type inferred into the val account?

Mmmm, I guess is ... Either<NegativeAmount, Account>?

Nope, the type is:

val account: Either<NegativeAmount, Either<NegativeAmount, Account>> = Account.create(100.toBigDecimal())
    .map { a -> a.deposit(100.toBigDecimal()) }

🤯 , either inception!

Remember map is a function that maps type A to type B, in our case, the function deposit(A):B:

  • A: BigDecimal type
  • B: Either<NegativeAmount, Account>

Therefore, we are applying a fn that not just transform the value, it wraps into a context to an already wrapped context.

Guess what,flatmap fixes this, because it expects a function that returns another contained value, fixing the mess for you:

sealed class Either<out A, out B> {
    class Left<A>(val value: A) : Either<A, Nothing>()
    class Right<B>(val value: B) : Either<Nothing, B>()

    fun <C> map(fn: (B) -> C): Either<A, C> = when (this) {
        is Right -> Right(fn(this.value))
        is Left -> this
    }
    fun <A, C> flatMap(fn: (B) -> Either<A, C>): Either<A, C> = when (this) {
        is Right -> fn(this.value)
        is Left -> this as Either<A, C>
    }
}

And finally:

val account: Either<NegativeAmount, Account> = Account.create(100.toBigDecimal())
    .flatMap { a -> a.deposit(100.toBigDecimal()) }

It is a simple thing, but it took me a while to get it, bind~flatmap is the most important function to understand, once you get it, you can use monads without struggling and guessing types.

Ok, any tip? suggestion?

Yes, think in types, never in what the monad is doing under the hood (implementation), then if you have any value wrapped in a monad such as SomeMonad of A, if you apply a fn:

  • If fn goes from A -> B apply map
  • If fn goes from A -> SomeMonad of B apply flatmap

Got it!

One more thing, remember that we said that all monads are functors? See this:

sealed class Either<out A, out B> {
    class Left<A>(val value: A) : Either<A, Nothing>()
    class Right<B>(val value: B) : Either<Nothing, B>()

    fun <C> map(fn: (B) -> C): Either<A, C> = flatMap { Right(fn(it)) }

    fun <A, C> flatMap(fn: (B) -> Either<A, C>): Either<A, C> = when (this) {
        is Right -> fn(this.value)
        is Left -> this as Either<A, C>
    }
}

map can be defined with unit and flatmap, that's one of the reasons why a monad can be a functor.

A Monad is a Workflow/Pipeline builder

Monads allow you to compose small operations to create workflows and achieve bigger purposes.

This statement seems broad and ambiguous to me

Remember what we said about functional programming at the very beginning? FP is all about make programs by composing functions.

So, this is what monads do, compose functions, chain functions, combine them to create workflows.

Show it to me

Compose deposit and withdraw

Let's introduce a new operation in the account, withdraw money, with this operation we are also introducing a new error:

data class Account private constructor(val balance: BigDecimal) {
    // Other methods

    fun withdraw(amount: BigDecimal): Either<AccountError, Account> =
        applyAmount(amount) { this.copy(balance = this.balance - it) }
            .flatMap {
                if ((balance - amount) < ZERO) Left(NotEnoughFunds) else Right(Account(balance - amount))
            }
}
sealed class AccountError {
    object NegativeAmount : AccountError()
    object NotEnoughFunds : AccountError()
}

Scenario 1: Jane wants to open an account with 100, afterwards she will deposit 100 and finally withdraw 250, therefore an error should pop-up, because Jane has not enough funds.

val account = Account.create(100.toBigDecimal())
    .flatMap { it.deposit(100.toBigDecimal()) }
    .flatMap { it.withdraw(250.toBigDecimal()) }
// account = Left(value=NotEnoughFunds)

Meh!, okayish ... but could you show me how we can use it in my fancy real application?

Sure, let's create some application Services/Use-Cases.

Scenario 2: We want to create a service to transfer money within two different accounts.

class TransferMoney {
  operator fun invoke(debtor: Account, creditor: Account, amount: BigDecimal): Either<AccountError, Pair<Account, Account>> =
    debtor
      .withdraw(amount)
      .flatMap { d -> creditor.deposit(amount).map { Pair(d, it) } }
}

Scenario 3: We want to get an account from a repository, add some cash, and save it back with the new state.

interface AccountRepository {
    fun findBy(userId: UUID): Either<AccountNotFound, Account>
    fun save(account: Account)
}
sealed class AccountError {
    object NegativeAmount : AccountError()
    object NotEnoughFunds : AccountError()
    object AccountNotFound : AccountError()
}

class DepositCash(private val repository: AccountRepository) {
  operator fun invoke(userId: UUID, amount: BigDecimal): Either<AccountError, Unit> =
    repository.findBy(userId)
      .flatMap { it.deposit(amount) }
      .map(repository::save)
}

Ok, finally you have my vote, at least I will consider it in my next project!

Summary and Conclusion

I hope this helped someone to understand monads or at least to awake some curiosity on the topic.

I have skipped on purpose several topics and important things regarding monads, such as applicatives (sequence, traverse), monad laws, fold family, monad comprehensions and a lot more, but, as I said at the very beginning, this is just an introduction, complex topics should be learnt step by step.

A recap, what is a Monad for an imperative mind?

  • A monad is a functional design pattern, that serves for several purposes such as represent emptiness, error handling, side effects ...
  • A monad is a type in a context, it wraps a type meaningfully.
  • "A monad can be mapped over", implements in one way or another the map function
  • A Monad is a couple of functions, unit and flatmap
  • A Monad is a Workflow/Computation builder, you can compose, chain and combine to create a business flow.
You might also like...
Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP)

Mockative Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP). Installation Mockative uses KSP to generate

Run Kotlin/JS libraries in Kotlin/JVM and Kotlin/Native programs

Zipline This library streamlines using Kotlin/JS libraries from Kotlin/JVM and Kotlin/Native programs. It makes it possible to do continuous deploymen

Kotlin-oop - Repositório criado para ser utilizado pelo projeto de Kotlin OOP desenvolvido em Kotlin nas aulas feitas através da plataforma Alura.

Projeto React OOP Repositório criado para ser utilizado pelo projeto de Kotlin OOP desenvolvido em Kotlin nas aulas feitas através da plataforma Alura

Kotlin-koans - Kotlin Koans are a series of exercises to get you familiar with the Kotlin Syntax

kotlin-koans-edu Kotlin Koans are a series of exercises to get you familiar with

A somewhat copy past of Jetbrain's code from the kotlin plugin repo to make it humanly possible to test Intellij IDEA kotlin plugins that work on kotlin

A somewhat copy past of Jetbrain's code from the kotlin plugin repo to make it humanly possible to test Intellij IDEA kotlin plugins that work on kotlin

Real life Kotlin Multiplatform project with an iOS application developed in Swift with SwiftUI, an Android application developed in Kotlin with Jetpack Compose and a backed in Kotlin hosted on AppEngine.

Conferences4Hall Real life Kotlin Multiplatform project with an iOS application developed in Swift with SwiftUI, an Android application developed in K

🚀Plugin for Android Studio And IntelliJ Idea to generate Kotlin data class code from JSON text ( Json to Kotlin )
🚀Plugin for Android Studio And IntelliJ Idea to generate Kotlin data class code from JSON text ( Json to Kotlin )

JsonToKotlinClass Hi, Welcome! This is a plugin to generate Kotlin data class from JSON string, in another word, a plugin that converts JSON string to

Android + Kotlin + Github Actions + ktlint + Detekt + Gradle Kotlin DSL + buildSrc = ❤️

kotlin-android-template 🤖 A simple Github template that lets you create an Android/Kotlin project and be up and running in a few seconds. This templa

LifecycleMvp 1.2 0.0 Kotlin  is MVP architecture implementation with Android Architecture Components and Kotlin language features
LifecycleMvp 1.2 0.0 Kotlin is MVP architecture implementation with Android Architecture Components and Kotlin language features

MinSDK 14+ Download Gradle Add to project level build.gradle allprojects { repositories { ... maven { url 'https://jitpack.io' }

Kotlin TodoMVC – full-stack Kotlin application demo

Kotlin full stack TodoMVC This project is an example implementation of the TodoMVC app written in Kotlin. More specifically, it's the Kotlin port of t

Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock
Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock

This kata is a Kotlin multiplatform version of the kata KataTODOApiClientKotlin of Karumi. We are here to practice integration testing using HTTP stub

Small kotlin library for persisting _single instances_ of kotlin data classes
Small kotlin library for persisting _single instances_ of kotlin data classes

PerSista Small library for persisting single instances of kotlin data classes. NB: PerSista uses typeOf() internally which is marked as @ExperimentalS

Annotation processor that generates a kotlin wrapper class for a java file, enabling named parameters for kotlin callers.

Annotation processor that generates a kotlin wrapper class for a java file, enabling named parameters for kotlin callers.

Opinionated Redux-like implementation backed by Kotlin Coroutines and Kotlin Multiplatform Mobile

CoRed CoRed is Redux-like implementation that maintains the benefits of Redux's core idea without the boilerplate. No more action types, action creato

This is an Kotlin Library that enables Annotation-triggered method call logging for Kotlin Multiplatform.

This is an Kotlin Library that enables Annotation-triggered method call logging for Kotlin Multiplatform.

Unidirectional Data Flow in Kotlin - Port of https://github.com/ReSwift/ReSwift to Kotlin
Unidirectional Data Flow in Kotlin - Port of https://github.com/ReSwift/ReSwift to Kotlin

ReKotlin Port of ReSwift to Kotlin, which corresponds to ReSwift/4.0.0 Introduction ReKotlin is a Redux-like implementation of the unidirectional data

Kotlin Leaning Notes from Udacity Course | Kotlin Bootcamp for Programmers by Google
Kotlin Leaning Notes from Udacity Course | Kotlin Bootcamp for Programmers by Google

Kotlin Beginners Notes These are all personal notes taken from the Udacity Course (ud9011) of Kotlin Bootcamp for Programmers by Google as well as oth

Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.

Module Saga Website can be found here Add in build.gradle.kts repositories { mavenCentral() } dependencies { implementation("io.github.nomisr

Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to
Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to "Go" actually "Kotlin" :-)

Microservices Kotlin gRPC Deployed in EC2, Check it out! This repo contains microservices written in Kotlin with BFF pattern for performing CRUD opera

Owner
Albert Llousas Ortiz
@N26
Albert Llousas Ortiz
A simple screen starter written in kotlin

screen-starter A simple screen starter written in kotlin. Compilation Use clean shadowJar! Usage Use java -jar screen-starter-1.0-SNAPSHOT.jar --confi

GrowlyX 3 Sep 1, 2021
This repository contains a simple script that lets you kill gradle and kotlin daemons.

AndroidDaemonKiller This repository contains a simple script that lets you kill gradle and kotlin daemons. After updating gradle or kotlin or checking

Paul Woitaschek 25 Dec 3, 2022
The Android Version in Kotlin of The Dialer App (in SwiftUI)

Dialer An intuitive USSD client to handle most of the common actions for you. Contains common MTN Rwanda USSD activation codes, which drastically simp

Cédric Bahirwe 1 Dec 14, 2021
This project is an investigation of Hystrix in the Kotlin language

from-paris-to-berlin-circuit-breaker Technologies used This project is an investigation of Circuit-Breakers in the Kotlin language ?? ?? ?? ?? ?? ?? U

João Filipe Sabino Esperancinha 3 Dec 8, 2022
Make your IDE play Wilhelm Scream effect when you are using unsafe !! operator in Kotlin

Make your IDE play Wilhelm Scream effect when you are using unsafe !! operator in Kotlin

Mikhail Levchenko 78 Nov 15, 2022
HackerRank - HackerRank solutions in Kotlin

Solutions to problems on HackerRank. If you are interested in helping or have a

Ahmed mahmoud abo elnaga 1 Jan 19, 2022
Editframe Kotlin Client library

Editframe Kotlin Client library Installing Add the project to your gradle dependencies.

editframe 2 Apr 7, 2022
MagicSnap A Snapchat Xposed Module in Kotlin to learn

MagicSnap A Snapchat Xposed Module in Kotlin to learn

J 18 Nov 8, 2022
Backing property explained - youtube video link in documnetation

backing property => Kotlin => Getter Setter and Backing Property Screenshot Inside android studio open a file press Alt+Shift+A and search for kotlin

Vishnu Sunilkumar 0 Nov 3, 2021
Repo: Programming problems with solutions in Kotlin to help avid Kotlin learners to get a strong hold on Kotlin programming.

Kotlin_practice_problems Repo: Programming problems with solutions in Kotlin to help avid Kotlin learners to get a strong hold on Kotlin programming.

Aman 0 Oct 14, 2021