Skip to main content

Command Palette

Search for a command to run...

Understanding Monads

How wrapping values can unwrap clarity

Published
•3 min read•View as Markdown
Understanding Monads
O

ONIXLabs strives to improve the software development experience through a commitment to building and providing exceptionally high quality, comprehensive and robust solutions for enterprise applications that scale.

What Even Is a Monad?

It’s often said that once you understand monads, you lose the ability to explain them clearly. But let’s give it a try anyway!

In category theory, a monad is defined as a monoid in the category of endofunctors, with functor composition as the binary operation and natural transformations as the morphisms.

If that made your brain seize up, you’re not alone. Unless you have a background in abstract mathematics, this definition is more confusing than enlightening.

A Practical Definition

In programming, a monad is a design pattern used to represent computations as a sequence of steps. More concretely, it's a type that wraps zero, one, or many values, and provides a structured way to apply operations to those values.

Monads let you chain operations together safely and predictably. It’s all about composable computation with context, where the "context" might be anything from "this might fail", or "this is missing", to "this runs asynchronously".

This chaining is made possible through a few key operations:

  • Constructors, static or implicit methods (return/pure) wrap a raw value in a monadic context.

  • Map/Select applies a function to the wrapped value, producing a new monad.

  • FlatMap/Bind/SelectMany applies a function that returns another monad and then flattens the result.

These operations must follow three laws (left identity, right identity, associativity) to qualify as a true monad—but you don’t need to memorise them to use monads effectively.

An Even More Practical Definition

In programming, a monad is a type that:

  1. Wraps zero, one, or many values.

  2. Chains computations while managing context, such as success or failure, presence or absence, or asynchronicity, to name a few.

Think of a monad as a computation wrapper. It holds onto a value and provides a structured way to apply operations to it.

The Monad Interface

Let’s break down a monad with standard terminology and examples.

  1. unit / pure / return wraps a value into a monadic context:

Haskell

wrapped :: Maybe Int
wrapped = Just 42

F#

let wrapped = Some 42

C# (Assumes the use of a 3rd party library)

Optional<int> wrapped = Optional.Some(42);

Kotlin (Assumes the use of a 3rd party library)

val wrapped: Optional<Int> = some 42

JavaScript (Assumes the use of a 3rd party library)

const wrapped = some(42);

In all of the examples above, wrapped indicates a some branch of the optional monad (sometimes called option, or maybe).

Railway Oriented Programming (ROP)

You might also hear monads described in the context of Railway Oriented Programming (ROP)—a metaphor where operations are chained like train cars, and errors or edge cases are handled along the way without derailing the whole process.

Still Feels Abstract?

Don’t worry—you’ve probably been using monads without realising it!

Most modern languages include monadic types for asynchronous computation: Task<T> in C#, CompletableFuture<T> in Java, and Promise<T> in JavaScript. These are often awaitable, letting you write imperative-looking code for asynchronous operations. Under the hood, they encapsulate a context of "this will complete later," so your code doesn’t have to manage that explicitly.

If you’ve written any C#, Java, or Kotlin, you've almost certainly used List<T>. And yes—List<T> is a monad! It models computations that yield multiple results. With familiar operations like Map, Select, FlatMap, and SelectMany, you’ve likely used these monadic methods directly to write expressive, declarative code without manual loops.

Conclusion

Monads aren’t magic or even that complicated. They’re just wrappers around zero, one, or many values that provide a way to chain computations safely and predictably. They help manage side effects, control flow, and composition in a clean, reusable way.

So the next time you await a Task, chain a thenCompose on a CompletableFuture, or use LINQ on a list—give yourself some credit. You’re using monads, whether you knew it or not.