Monads explained in Kotlin

Albert Llousas
9 min readDec 20, 2021

--

The scary and angry monad symbol

Throughout my career as a software engineer, I have always tried to stay current and learn new things. Among others areas, functional programming caught my attention, leading me to encounter a scary term: 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.

After some time, I understood (I hope) what a monad is, and I am going to explain it. So here I go 🤘.

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

First things first: Some theory

What is a function?

In mathematics, a function is a relationship between two data sets, where each element of one corresponds to an element of the other. Based on that definition, we can also define a function as a relationship between two types, since a data type is a set of values.

What is Functional Programming?

FP (functional programming) is a programming paradigm where programs are created by composing functions.

FP is based on λ-calculus (compose and transform) instead of Imperative Programming, which relies on the Von Neumann model (state mutation).

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 provides a different way of solving problems.

I had enough; please, explain the topic

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

The Bank Account

Consider a straightforward implementation of a bank account:

What is the challenge here?

Performing operations on bank accounts, such as depositing or withdrawing, can encounter failures. Let’s explore how we can handle these errors using monads.

Why not just throw exceptions?

We’ll continue using exceptions, but in a different way:

  • Are we dealing with a crash? Then let it crash, let the exception fly, after all, they are exceptional cases, we will deal with them outside our domain, at the app’s boundary.
  • Is it an error that you can control? Then employ a different error handling mechanism.

Why don’t use exceptions for everything?

  • In most programming languages, or when using lambdas, exceptions can’t be part of the function signatures. From a client’s perspective, you may not even be aware of these edge cases and might not handle them properly.
  • When using exceptions for non-happy case scenarios, a client is not forced to face the errors; they can easily let them crash, delegating the the control of business flows 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

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
  • Collections: List monad
  • Many others …

Either Monad

For our exercise we are going to focus on the `Either` monad 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 correct.

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 basic implementation of either:

Bla bla bla, boring, show me something real

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 business rules for our account.

The code from the consumer perspective looks like this:

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 succeed

Amazing 😍, isn’t it?

I still prefer my amazing code throwing exceptions

Are you sure? The same signature with exceptions:

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:

- Add an amazing documentation.

- Check the implementation.

- Come back to java where I can use checked exceptions.

Well:

  1. Documentation: not clean, code should be self-explanatory, 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.

Ok, fine, you got me

A monad can be mapped over

We can not explain monads without explaining 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, mainly in collections when you want to map over all the elements and change them given a lambda.

Functor implementations differ depending on the programming language, but as a concept, a functor can be seen as:

  • A container, that holds a type, A
  • A function to change the inner value type from (A -> B) , usually called map

Here one of the possible implementations, using an interface:

Interesting, I was using functors without knowing it … 🤦‍ But why is this 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 would I need this for our Account example?

Depositing money into the bank account

Now let’s try to add money:

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

map to the rescue!

And …

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?

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:

  • First one, wrapping a value in a monad (the container), called return or unit
  • Second one, also known as bind or flatmap , to apply a function to the contained value that outputs another monad:

What the f***? Please, why would I even need these functions?

Let’s explain this:

Depositing money into the bank account, second try

Remember our function to deposit money without errors:

Let’s add some errors here:

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:

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

Mmmm, I guess it is … Either<NegativeAmount, Account>?

Nope, the type is:

🤯, 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> type

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

Applying a fn that wraps to a map

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

And finally:

bind~flatmap is the most important function when it comes to monads, once you get it, you can use any monad 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, when you any fn:

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

Got it!

A Monad is a Workflow/Pipeline builder

Monads allow you to compose small operations to 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

Composing deposit and withdraw

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

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 not have enough funds.

Scenario 2: A service that transfers money between two different accounts.

Scenario 3: Combine different actions, find an account, add some cash, and save it back with the new state.

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

Summary and Conclusion

I hope this has helped you understand monads, or at least awakened some curiosity about the topic.

I have intentionally skipped several topics and important aspects regarding monads, such as applicatives (sequence, traverse), monad laws, the fold family, monad comprehensions, and much more. This is merely an introduction; complex topics should be learned step by step.

To recap, what is a Monad?

  • 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 them to create a business flow.

You can find all the sources in my github

--

--