---
title: "Monads for Kotlin Developers"
description: "A practical exploration of monads through Kotlin: nullable chains, flatMap, typed errors, coroutines, and Arrow."
url: "https://ticis.eu/blog/monads-for-kotlin-developers/"
published: "2026-09-13"
---

# Monads for Kotlin Developers

The word *monad* can make ordinary programming sound more mysterious than it is. Kotlin developers already use many of the underlying ideas: nullable call chains, `List.flatMap`, `Result`, sequential suspending functions, and Arrow's typed-error DSLs all connect computations according to some rule.

They are not all interchangeable, and not every API in that list is itself a monad. They are useful starting points because they let us derive the idea from Kotlin code instead of beginning with Haskell notation.

This exploration develops one practical definition:

> A monad is a parameterized type shape with lawful operations for putting a value into that shape and sequencing shape-producing computations.

Those operations are conventionally called `pure` and `flatMap`. The rest of the definition, especially *type shape* and *lawful*, will become more concrete through this exploration.

## 1. Start With a Kotlin Problem

Suppose an application loads an account in three steps:

```kotlin
fun parseUserId(input: String): UserId? = TODO()
fun findUser(id: UserId): User? = TODO()
fun loadAccount(user: User): Account? = TODO()
```

Each function either produces the value needed by the next function or returns `null`. One direct implementation is:

```kotlin
fun accountFor(input: String): Account? {
    val id = parseUserId(input) ?: return null
    val user = findUser(id) ?: return null
    return loadAccount(user)
}
```

This is good Kotlin. The control rule is visible: continue when a value exists; otherwise return `null`.

The same rule can be factored into a reusable operation:

```kotlin
inline fun <A : Any, B : Any> A?.flatMap(
    next: (A) -> B?
): B? = if (this == null) null else next(this)
```

Now the workflow can be written as a chain:

```kotlin
fun accountFor(input: String): Account? =
    parseUserId(input)
        .flatMap(::findUser)
        .flatMap(::loadAccount)
```

`flatMap` models the repeated decision:

- If the current value is `null`, keep `null` and do not call `next`.
- If the current value is present, pass it to `next`.

The business functions only perform their own step. The sequencing operation handles the computational rule shared by every step.

Kotlin already offers essentially this nullable behavior through a safe call and `let`:

```kotlin
fun accountFor(input: String): Account? =
    parseUserId(input)
        ?.let(::findUser)
        ?.let(::loadAccount)
```

`let` alone is just a scope function. The safe-call operator is what skips the call when the receiver is `null`. Together they behave like nullable `flatMap` in this example.

This gives us the first useful intuition:

> Monadic sequencing factors out a repeated rule for deciding how one result reaches the next computation.

For nullable values that rule is short-circuiting on absence. Because the sequencing operation is implemented with ordinary code, other types can use very different rules: they might branch into several results, carry updated state, preserve a failure, or defer work. This freedom is what makes monads appear so diverse. Before looking for what they have in common, it helps to explore another familiar rule.

## 2. Why `flatMap` Has That Name

Under value-based semantics and pure transformations, Kotlin's `List` is one concrete example of a monad. We do not need its formal definition yet. Its familiar `map` and `flatMap` operations show where the name `flatMap` comes from and introduce a different sequencing rule: branch into zero or more results, then concatenate them.

`map` expects a function that returns an ordinary value:

```kotlin
val lengths: List<Int> =
    listOf("Ada", "Grace").map(String::length)

// [3, 5]
```

The list structure remains, while each inner value changes from `String` to `Int`.

If the transformation itself returns a list, `map` preserves both list layers:

```kotlin
val nested: List<List<Char>> =
    listOf("Ada", "Grace").map(String::toList)

// [[A, d, a], [G, r, a, c, e]]
```

`flatMap` maps the function and flattens the resulting nested lists:

```kotlin
val characters: List<Char> =
    listOf("Ada", "Grace").flatMap(String::toList)

// [A, d, a, G, r, a, c, e]
```

The general shapes are:

```text
map:     M<A> + (A -> B)    -> M<B>
flatMap: M<A> + (A -> M<B>) -> M<B>
```

If we used `map` with the second function, its result would be `M<M<B>>`. `flatMap` removes one repeated `M` layer and returns `M<B>`.

For Kotlin's `List`, removing that layer means concatenating the inner lists in order while preserving duplicates. Concatenation is a natural rule for lists, but the general `flatMap` shape does not prescribe it. An API could choose a different rule, such as keeping only the first occurrence of each result:

```kotlin
inline fun <A, B> List<A>.flatMapUnique(
    transform: (A) -> Iterable<B>
): List<B> = flatMap(transform).distinct()

val uniqueCharacters: List<Char> =
    listOf("Ada", "Grace").flatMapUnique(String::toList)

// [A, d, a, G, r, c, e]
```

This has the same broad input and output shape as `flatMap`, but it represents different behavior: duplicate results disappear. A matching shape alone does not make every conceivable rule a monad. The laws introduced later rule out some implementations or require a different outer abstraction, such as a set or a type that guarantees unique elements.

Nullable types hide this distinction because Kotlin collapses repeated nullability: `Account??` is not a distinct type from `Account?`. Lists make the flattening visible.

## 3. The Monad Shape

A monad has two fundamental operations:

```text
pure:    A -> M<A>
flatMap: M<A> + (A -> M<B>) -> M<B>
```

Read `M<A>` as "an `A` in some computational structure `M`." Depending on `M`, that structure might represent:

- A collection data structure such as `List<A>`, interpreted as zero or more possible results
- A value that may be absent
- A success or a typed failure
- An explicit state transition
- A deferred calculation
- An action that may read a file, send a network request, or print output before producing an `A`, represented as `IO<A>` in Haskell

`pure` puts an already available value into the structure without adding any extra behavior. Examples include:

```text
Nullable: value
List:     listOf(value)
Either:   Right(value)
Logged:   Logged(value, emptyList())
```

The name `pure` is historical terminology. Calling `pure(impureCall())` does not undo a side effect that already occurred, and it does not prove that arbitrary Kotlin code has no observable effects.

`flatMap` receives both a structured value and a function that chooses the next structured computation. The outer structure stays the same while the successful or produced value may change from `A` to `B`.

For example, a typed-error chain might move through these types:

```text
Outcome<LookupError, UserId>
Outcome<LookupError, User>
Outcome<LookupError, Account>
```

The value type changes at each step. The `Outcome<LookupError, ...>` shape and its error-handling rule remain.

### Why This Is Awkward to Express Generically in Kotlin

`M` in `M<A>` is not an ordinary complete type. It is a type constructor: something waiting for a type argument. Examples are `List<...>`, nullable `... ?`, and `Outcome<LookupError, ...>`.

Kotlin can declare `List<A>`, but it cannot directly declare a type parameter meaning "any `M` for which `M<A>` is valid." This feature is usually called *higher-kinded types*.

That is why Kotlin does not have one natural standard interface like this pseudocode:

```kotlin
// Pseudocode: M cannot be declared this way in Kotlin.
interface Monad<M> {
    fun <A> pure(value: A): M<A>
    fun <A, B> flatMap(value: M<A>, next: (A) -> M<B>): M<B>
}
```

Kotlin libraries usually put `map` and `flatMap` on concrete types instead. The concept is generic even when the language API is not.

## 4. Build One in Ordinary Kotlin

Here is a small `Outcome` type. It is the same broad shape as a right-biased `Either`: failure is stored on one side, success on the other, and operations such as `map` and `flatMap` act on the successful side.

```kotlin
sealed interface Outcome<out E, out A> {
    data class Failure<E>(val error: E) : Outcome<E, Nothing>
    data class Success<A>(val value: A) : Outcome<Nothing, A>
}
```

Its `pure` operation creates a success:

```kotlin
fun <E, A> pureOutcome(value: A): Outcome<E, A> =
    Outcome.Success(value)
```

Its `flatMap` operation defines the sequencing rule:

```kotlin
inline fun <E, A, B> Outcome<E, A>.flatMap(
    next: (A) -> Outcome<E, B>
): Outcome<E, B> = when (this) {
    is Outcome.Failure -> this
    is Outcome.Success -> next(value)
}
```

On failure, `flatMap` preserves the error and skips `next`. On success, it passes the successful value to `next`.

That is enough to compose an entire workflow:

```kotlin
fun parseUserId(input: String): Outcome<LookupError, UserId> = TODO()
fun findUser(id: UserId): Outcome<LookupError, User> = TODO()
fun loadAccount(user: User): Outcome<LookupError, Account> = TODO()

fun accountFor(input: String): Outcome<LookupError, Account> =
    parseUserId(input)
        .flatMap(::findUser)
        .flatMap(::loadAccount)
```

`map` can now be derived from `flatMap` and `pure`:

```kotlin
inline fun <E, A, B> Outcome<E, A>.map(
    transform: (A) -> B
): Outcome<E, B> =
    flatMap { value -> pureOutcome(transform(value)) }
```

The same derivation works for `List`. Here, `listOf` plays the role of `pure` by putting each transformed value into a one-element list:

```kotlin
inline fun <A, B> List<A>.mapViaFlatMap(
    transform: (A) -> B
): List<B> =
    flatMap { value -> listOf(transform(value)) }

val lengths: List<Int> =
    listOf("Ada", "Grace").mapViaFlatMap(String::length)

// [3, 5]
```

`flatMap` invokes `transform` for every element and concatenates the resulting one-element lists. The result is the same as calling the standard `List.map`.

Formally, `flatMap` alone cannot derive `map`; it also needs a way to put the transformed `B` back into the structure. Haskell exposes that operation as `pure` through `Applicative`, which every `Monad` must provide. Generic Haskell code can call it without knowing whether the concrete type is a list, `Maybe`, or `Either`.

Kotlin has no shared `Monad` interface, so application code rarely uses the name `pure`. Concrete constructors such as `listOf`, `Outcome.Success`, and Arrow's `Either.Right` fill that role, and each type normally provides `map` directly.

### A Different Rule: Accumulating Messages

We have already seen that monads can take different forms: nullable values short-circuit on absence, while lists branch and concatenate results. A pattern traditionally called Writer provides another rule: continue normally while accumulating messages as data. We will call our small Kotlin version `Logged`.

```kotlin
data class Logged<A>(
    val value: A,
    val messages: List<String>,
) {
    fun <B> flatMap(next: (A) -> Logged<B>): Logged<B> {
        val result = next(value)
        return Logged(
            value = result.value,
            messages = messages + result.messages,
        )
    }

    companion object {
        fun <A> pure(value: A): Logged<A> =
            Logged(value, emptyList())
    }
}
```

`Logged<A>` contains a value and the messages collected while producing it. `flatMap` passes the value to `next`, takes the new value, and appends the new messages to those already collected.

For example, a price calculation can retain an explanation of each adjustment:

```kotlin
fun applyDiscount(total: Int): Logged<Int> =
    Logged(total - 10, listOf("Applied discount: -10"))

fun addShipping(total: Int): Logged<Int> =
    Logged(total + 5, listOf("Added shipping: +5"))

val checkout: Logged<Int> =
    Logged.pure(100)
        .flatMap(::applyDiscount)
        .flatMap(::addShipping)

// checkout.value == 95
// checkout.messages ==
//     ["Applied discount: -10", "Added shipping: +5"]
```

The chain carries one value forward and preserves the messages in order. There is no short-circuiting or branching.

The messages are ordinary data, not calls to an external logging system. This pattern can be useful for calculation explanations, audit details, or generated warnings that a caller must inspect. For a small fixed calculation, returning a named result object directly is often simpler than introducing `Logged`.

`Outcome` and `Logged` have the same operation shapes but different sequencing rules.

| Type shape | Meaning | Sequencing rule |
| --- | --- | --- |
| `A?` | A value may be absent | Continue with a present value; stop on `null` |
| `Outcome<E, A>` | A value or typed failure | Continue on success; preserve failure |
| `List<A>` | Zero or more values | Run the next step for every value; concatenate the results |
| `Logged<A>` | A value plus accumulated messages | Pass the value forward; append messages in order |
| `suspend () -> A` with suitable operations | A suspending calculation | Run calculations in sequence and pass the result onward |

The word *context* is often used for the structure represented by `M`. In this document it does not mean Kotlin's `CoroutineContext`.

## 5. The Laws: What Makes the Operations Predictable

Any methods can be named `pure` and `flatMap`. A monad requires them to obey three laws.

Suppose these declarations exist:

```text
val value: A
val computation: M<A>
fun firstOperation(value: A): M<B>
fun secondOperation(value: B): M<C>
```

The laws are:

```text
// Left identity
pure(value).flatMap(::firstOperation) == firstOperation(value)

// Right identity
computation.flatMap(::pure) == computation

// Associativity
computation.flatMap(::firstOperation).flatMap(::secondOperation) ==
    computation.flatMap { value ->
        firstOperation(value).flatMap(::secondOperation)
    }
```

The `==` signs mean equivalent behavior, not necessarily Kotlin's `equals` implementation or object identity. For collection monads, the equations assume pure transformation functions and compare the resulting values. Kotlin permits side effects inside those functions; such effects can expose evaluation order that lies outside this value-based model.

### Left Identity

Putting a value into the minimal context and immediately sequencing `firstOperation` must behave like calling `firstOperation` directly. `pure` cannot secretly add logging, failure, extra list elements, or another observable action.

### Right Identity

Sequencing a computation with `pure` must not change it. This law catches implementations that accidentally run work early, duplicate it, discard information, or alter state.

### Associativity

Regrouping a chain must not change its meaning. This lets libraries build long chains without parentheses deciding their behavior.

Associativity does **not** permit reordering:

```text
(m flatMap firstOperation) flatMap secondOperation
```

may be regrouped, but `secondOperation` may not be moved before `firstOperation`. Later computations often depend on earlier results, and observable actions may depend on their order.

For asynchronous or effectful APIs, equivalence must include relevant behavior such as evaluation time, cancellation, failure, resource lifetime, and produced side effects. Matching generic signatures alone is not enough.

### How Relevant Are These Laws in Kotlin?

Most Kotlin application developers do not need to prove these laws or think about them while writing ordinary `?.let`, collection, `Result`, or Arrow code. The laws matter most when implementing a type such as `Logged`, designing a library operation named `flatMap`, or testing whether a custom sequencing rule is predictable.

Associativity is not limited to code that explicitly stores or combines lambda values. Every `flatMap` call already accepts a function describing what happens next, even when that function is written as a method reference. The law becomes practical when a chain is extracted into a helper:

```kotlin
val inlineWorkflow: Logged<Int> =
    Logged.pure(100)
        .flatMap(::applyDiscount)
        .flatMap(::addShipping)

fun discountedThenShipped(total: Int): Logged<Int> =
    applyDiscount(total).flatMap(::addShipping)

val extractedWorkflow: Logged<Int> =
    Logged.pure(100).flatMap(::discountedThenShipped)
```

Associativity says that `inlineWorkflow` and `extractedWorkflow` have the same value and messages. In this pure workflow, extracting a named function changes the grouping but not the result.

This kind of regrouping is uncommon in small Kotlin chains, and standard-library or Arrow types have already made the relevant design choices. The laws support value-preserving refactors of pure code; they do not make side-effectful callbacks freely interchangeable. They become a direct engineering concern when defining a new `flatMap`, especially around evaluation timing, concurrency, cancellation, or resources.

## 6. Monad-Shaped Ideas in the Kotlin Standard Library

Kotlin does not label APIs as monads, but several familiar APIs expose parts of the pattern.

### Nullable Values

The opening example gave `A?` both required operations: an ordinary non-null value can be used as `A?`, and `?.let` provides the sequencing behavior. This is the nullable monad in practical terms. A successful `null` cannot be distinguished from absence, so this model only has one meaning for `null`.

The idiomatic Kotlin spelling is usually `?.let`, `?:`, or direct early returns, not a custom method called `flatMap`:

```kotlin
val city: City? = user
    ?.let(::loadAddress)
    ?.let(::loadCity)
```

The monad vocabulary explains the composition rule; it does not require replacing readable Kotlin control flow with abstract terminology.

### Lists and Other Collections

For `List<A>`, `listOf(value)` plays the role of `pure` and `List.flatMap` is already in the standard library:

```kotlin
data class Order(
    val number: String,
    val items: List<String>,
)

val orders = listOf(
    Order("A-100", listOf("keyboard", "mouse")),
    Order("A-101", listOf("monitor")),
)

val packingList: List<String> = orders.flatMap { order ->
    order.items.map { item -> "${order.number}: $item" }
}

// [
//     "A-100: keyboard",
//     "A-100: mouse",
//     "A-101: monitor",
// ]
```

Each order produces its own list of packing entries. `flatMap` runs that transformation for every order and concatenates the results into one packing list. This is List's sequencing rule: one value may branch into zero, one, or many next values.

#### Side Note: Collections as Game-Tree Search

The List monad can model nondeterministic choice: one position may have many possible next positions. For a chess board, a function can return every board reachable by one legal move:

```kotlin
val initialBoard: Board = TODO()

fun legalNextBoards(board: Board): List<Board> = TODO()

val positionsAfterThreePlies: List<Board> =
    listOf(initialBoard)
        .flatMap(::legalNextBoards)
        .flatMap(::legalNextBoards)
        .flatMap(::legalNextBoards)
```

The first `flatMap` produces every position after one move, the second expands every one of those positions, and the third does so again. The resulting list is one level of the game tree after three *plies*, where one ply is one player's move.

This is the usual List rule, not a different List monad. It preserves order and may contain the same position more than once when different move sequences reach it. If an algorithm only cares about unique positions, a set-like abstraction can use a different flattening rule: union the branches and discard duplicates.

```kotlin
inline fun <A, B> Set<A>.flatMapToSet(
    next: (A) -> Set<B>
): Set<B> = buildSet {
    this@flatMapToSet.forEach { value ->
        addAll(next(value))
    }
}

fun uniqueLegalNextBoards(board: Board): Set<Board> =
    legalNextBoards(board).toSet()

val uniquePositionsAfterTwoPlies: Set<Board> =
    setOf(initialBoard)
        .flatMapToSet(::uniqueLegalNextBoards)
        .flatMapToSet(::uniqueLegalNextBoards)
```

Here `setOf(value)` plays the role of `pure`, and `flatMapToSet` combines branches with set union. This is a Set-style monad rather than a List monad. `Board.equals` must include every rule-relevant part of the position before deduplication is safe.

Real chess engines cannot expand the full tree for long because the number of positions grows rapidly. They combine this branching model with evaluation, pruning, caching, and algorithms such as minimax with alpha-beta pruning. The monadic view explains the branching step; it is not the complete search algorithm.

### Kotlin `Result`

`Result<A>` represents either a successful `A` or failure with a `Throwable`. The standard library provides operations such as `map`, `mapCatching`, `recover`, and `fold`, but it does not provide `flatMap` under that name.

An application can define one:

```kotlin
inline fun <A, B> Result<A>.flatMap(
    transform: (A) -> Result<B>
): Result<B> = fold(
    onSuccess = transform,
    onFailure = { error -> Result.failure(error) },
)
```

Rewriting the account example so every step returns `Result` gives the familiar shape:

```kotlin
fun parseUserId(input: String): Result<UserId> = TODO()
fun findUser(id: UserId): Result<User> = TODO()
fun loadAccount(user: User): Result<Account> = TODO()

fun accountFor(input: String): Result<Account> =
    parseUserId(input)
        .flatMap(::findUser)
        .flatMap(::loadAccount)
```

This extension preserves an existing `Result.failure`, but an exception thrown by `transform` escapes. Another implementation could catch it and return a failure instead. That choice needs a clear policy for fatal exceptions and coroutine cancellation; the monad abstraction does not choose one.

### Scope Functions Are Building Blocks, Not Monads

Functions such as `let`, `run`, and `also` are higher-order functions: they accept another function. That alone does not make them monads.

```kotlin
val length: Int = "Kotlin".let(String::length)
```

Plain `let` has no reusable outer type shape and no short-circuiting rule. In `value?.let(...)`, the nullable safe-call supplies the interesting structure. This distinction is useful whenever familiar syntax looks "monadic": identify which type or language feature actually controls sequencing.

## 7. Suspending Functions and Coroutines

Sequential suspending Kotlin often looks like direct-style monadic code:

```kotlin
suspend fun fetchAccount(): Account {
    val user = fetchUser()
    return fetchAccountFor(user)
}
```

The result of `fetchUser` is assigned a local name, and the next suspending call can depend on it. This is similar in spirit to *do notation* in languages that expose monadic sequencing directly.

The resemblance has limits. `suspend` allows a function to call other suspending functions and to pause at suspension points. It does not launch a coroutine, imply concurrency, turn blocking calls into non-blocking ones, or record side effects in the return type.

A family of suspending function values can be given monad-shaped operations explicitly:

```kotlin
typealias Suspended<A> = suspend () -> A

fun <A> suspended(value: A): Suspended<A> = { value }

fun <A, B> flatMapSuspended(
    first: Suspended<A>,
    next: (A) -> Suspended<B>,
): Suspended<B> = {
    val value = first()
    next(value)()
}
```

The type shape here is `suspend () -> A`, not the keyword `suspend` by itself. Exception, cancellation, and coroutine-context behavior are part of this type's observable behavior, so they also matter when judging the laws.

Ordinary sequential calls are clearer than wrapping every suspending function in `Suspended`. The model explains why direct-style code composes; Kotlin lets us write that composition without spelling out `flatMap`.

### `Deferred<A>` Is a Work Handle

`Deferred<A>` is a result-bearing `Job`, usually created by `async`. The work may not have started yet, may be running, or may already be complete, failed, or cancelled. `await()` is a suspending call that returns the successful result or rethrows the deferred work's failure. It throws `CancellationException` when the deferred work or the awaiting coroutine is cancelled; prompt cancellation means caller cancellation can win even when a result has just become available.

```text
await: suspend (Deferred<A>) -> A
```

That differs from `flatMap`:

```text
flatMap: Deferred<A> + (A -> Deferred<B>) -> Deferred<B>
```

`await()` has no next function and returns a plain value. The standard `Deferred` API has no monadic `pure` and `flatMap` pair.

A library could add such operations, but it would have to define who owns the combined coroutine, when each stage starts, and how failure, cancellation, and `CoroutineContext` propagate. Kotlin normally avoids that extra abstraction: await the result inside structured suspending code, use `Deferred<A>` when separately started work needs a result handle, and use `Job` when only completion matters.

## 8. Arrow: Concrete Types and Direct-Style Contexts

[Arrow](https://arrow-kt.io/) brings typed functional patterns to Kotlin. Current Arrow favors concrete data types and receiver-based DSLs rather than one generic `Monad<F>` interface.

The examples in this chapter use Arrow **2.2.3**. Older tutorials may show a very different API.

### `Either<E, A>` and `flatMap`

Arrow's `Either<E, A>` represents `Left(error)` or `Right(value)`. Its `flatMap` acts on the right, successful side and follows the same rule as the earlier `Outcome` example:

```kotlin
fun parseUserId(input: String): Either<DomainError, UserId> = TODO()
fun findUser(id: UserId): Either<DomainError, User> = TODO()
fun loadAccount(user: User): Either<DomainError, Account> = TODO()

fun accountFor(input: String): Either<DomainError, Account> =
    parseUserId(input)
        .flatMap(::findUser)
        .flatMap(::loadAccount)
```

`Left` skips the remaining functions; `Right` supplies its value to the next function. `Either.Right(value)` plays the role of `pure`.

This is the most direct correspondence between the formal monad operations and a common Kotlin library API.

### `Raise<E>` and `bind()`

Arrow also supports direct-style typed errors through `Raise` and builders such as `either`:

```kotlin
fun accountFor(
    input: String
): Either<DomainError, Account> = either {
    val id = parseUserId(input).bind()
    val user = findUser(id).bind()
    loadAccount(user).bind()
}
```

Inside `either`, `bind()` returns the value from `Right` or ends the block with the error from `Left`. The statements that follow describe what happens after success; in functional terminology, they form the *continuation*.

This has the same success/failure sequencing behavior as the `Either.flatMap` chain, but Arrow's `bind()` is not itself the formal two-input operation:

```text
formal flatMap: Either<E, A> + (A -> Either<E, B>) -> Either<E, B>
Raise bind:     Either<E, A> -> A inside a surrounding Raise<E> scope
```

The surrounding block supplies the rest of the computation, so the code reads like ordinary sequential Kotlin while retaining typed short-circuiting.

`Raise<E>` has one central operation:

```kotlin
interface Raise<in E> {
    fun raise(error: E): Nothing
}
```

Because `raise` returns `Nothing`, execution does not continue after the call. That makes `Raise` suitable for early exit, not for operations such as reading and updating State that must resume with a value.

### Arrow and Suspending Functions Solve Different Problems

A suspending function can also use typed errors:

```kotlin
suspend fun Raise<DomainError>.accountFor(input: String): Account {
    val id = parseUserId(input).bind()
    val user = fetchUser(id).bind()
    return loadRemoteAccount(user).bind()
}
```

`suspend` permits suspension; `Raise<DomainError>` provides typed early exit. Cancellation is observed at cancellable suspension points or through explicit checks. Thrown exceptions and cancellation remain separate from `DomainError`, and `Raise` does not track network or database access.

When nested computations use different error types, the program must define how they enter the surrounding domain error. Arrow provides operations such as `withError`, while `Either.mapLeft` can perform the conversion before `bind`.

### Current Arrow and Old Arrow Are Different

Arrow 2.2.3 has no generic `Monad<F>` interface or higher-kinded-type encoding. It also no longer ships the old `State`, `StateT`, `Reader`, `Writer`, or `WriterT` APIs.

Current Arrow favors concrete types and focused scopes. `Option`, `Either`, `Eval`, `NonEmptyList`, and `Ior` each have their own composition operations. `Raise` handles typed short-circuiting, while `Resource` manages acquisition and cleanup. These APIs do not implement one shared monad interface.

Code importing `arrow.typeclasses.Monad`, `arrow.mtl.State`, or `StateT` belongs to older Arrow releases.

## 9. Choosing the Right Kotlin Representation

Monad vocabulary is useful when it helps choose a concrete type. Start with what the caller needs to know.

### Describe the Result

| The caller needs | A typical choice |
| --- | --- |
| A value, or no value, with no explanation | `A?` |
| Success or an exception from a boundary call | `Result<A>` with a clear catching policy |
| Success or an expected domain failure | Arrow `Either<E, A>` or `Raise<E>` |
| Zero or more answers | `List<A>` or another collection |
| A value together with explanations or warnings | A named result type, or `Logged<A>` when the pattern repeats |

For the account example, nullable results are enough if callers only ask, "Did we find an account?" Use `Either` or `Raise` when they must distinguish invalid input, a missing user, and an unavailable account.

### Describe Execution Separately

Result types and execution types answer different questions. A function may suspend and still return an `Either`:

```kotlin
suspend fun loadAccount(
    input: String
): Either<DomainError, Account> = TODO()
```

Here `suspend` describes how the call runs, while `Either` describes its normal result. Use `Deferred<A>` only when separately started work needs a result handle, and `Job` when only completion matters.

### Keep the Abstraction Proportionate

For a short, fixed workflow, direct Kotlin is usually best: early returns, safe calls, `mapIndexed`, or a small named result class. Reach for a reusable abstraction when the same sequencing rule appears throughout an API or when the type communicates behavior that callers must handle. The next chapter looks at State, a useful but deliberately advanced example of that tradeoff.

## 10. State and Context Order

State shows how far the same `pure` and `flatMap` pattern can stretch.

### A State Transition Is a Function

The following `State` type is ordinary Kotlin, not an Arrow 2.2.3 API. It stores a state transition as a function:

```kotlin
class State<S, A>(
    val run: (S) -> Pair<S, A>
) {
    fun <B> flatMap(next: (A) -> State<S, B>): State<S, B> =
        State { initialState ->
            val (updatedState, value) = run(initialState)
            next(value).run(updatedState)
        }

    companion object {
        fun <S, A> pure(value: A): State<S, A> =
            State { state -> state to value }
    }
}
```

`State<S, A>` does not hold the current state. Its `run` function accepts an initial `S` and returns the updated state together with an `A`.

```text
initial S -> run -> (updated S, produced A)
```

`flatMap` builds a larger transition: run the first transition, use its value to choose the next one, then run that transition with the updated state.

For example, an integer state can represent the next sequence number:

```kotlin
fun numbered(name: String): State<Int, String> =
    State { nextNumber ->
        (nextNumber + 1) to "$nextNumber. $name"
    }

val numberNames: State<Int, List<String>> =
    numbered("Ada").flatMap { ada ->
        numbered("Grace").flatMap { grace ->
            State.pure<Int, List<String>>(listOf(ada, grace))
        }
    }

val (nextNumber, labels) = numberNames.run(1)

// nextNumber == 3
// labels == ["1. Ada", "2. Grace"]
```

The first transition receives `1` and returns `2`; the second receives `2` and returns `3`. No shared variable is mutated. For this fixed numbering task, `mapIndexed` would be simpler.

State becomes more useful when the same state must pass through many reusable operations. A compiler, for example, may carry a temporary-name counter, a symbol table, and emitted instructions:

```kotlin
fun freshTemporary(): State<CompilerState, Temporary> = TODO()
fun declare(temporary: Temporary): State<CompilerState, Symbol> = TODO()
fun emitStore(symbol: Symbol): State<CompilerState, Unit> = TODO()

val compileAssignment: State<CompilerState, Unit> =
    freshTemporary().flatMap { temporary ->
        declare(temporary).flatMap { symbol ->
            emitStore(symbol)
        }
    }
```

Each operation receives the compiler state left by the previous one. Without `State`, callers would repeatedly unpack `Pair<CompilerState, A>` and forward the first component. The wrapper earns its keep only when that plumbing appears across many workflows. It is unrelated to synchronization or database transactions.

### Context Order Changes Meaning

Combining State with typed failure requires a choice. Here, order means which context sits on the outside: failure around State, or State around failure. The two types report different information after failure:

```kotlin
typealias StateEither<S, E, A> =
    (S) -> Either<E, Pair<S, A>>

typealias StateWithError<S, E, A> =
    (S) -> Pair<S, Either<E, A>>
```

`StateEither` loses the final state when it returns `Left`. `StateWithError` always returns a state, even when its value is `Left`. Choose based on whether partial progress should remain visible. Neither type can roll back a database write or shared mutation; external rollback still needs a transaction.

Haskell libraries often package recurring combinations in *monad transformers*. Current Kotlin usually uses a concrete combined type, an explicit parameter, or a focused scope such as `Raise`. The extra specificity is often helpful here because behavior such as retaining state on failure stays visible.

## 11. Reading Monad Terminology

Kotlin usually presents these ideas through concrete methods and scopes. Haskell exposes the common abstraction more directly, so its vocabulary appears often in articles and API documentation.

### Haskell and Arrow Spell the Ideas Differently

| Term | Meaning |
| --- | --- |
| `pure(value)` | Put a value into the current monadic structure |
| bind | The monadic sequencing operation called `flatMap` in Kotlin-style APIs |
| `>>=` | Haskell's operator for bind |
| do notation | Haskell syntax for naming intermediate results in a monadic chain |
| `Raise.bind()` | Extract a successful value inside an Arrow `Raise` scope |

The same `Either` chain can appear in two forms:

```text
Kotlin: parse(input).flatMap(::find).flatMap(::load)
Haskell: parse input >>= find >>= load
```

Arrow's `Raise.bind()` gives similar short-circuiting behavior in direct-style code, but it is not literally the two-input `flatMap` operation. The surrounding `either` or `Raise` block supplies the rest of the computation.

### What the Abstraction Leaves Open

**Sequencing behavior varies.** Lists branch, `Outcome` stops on failure, `Logged` accumulates messages, and State passes a state value explicitly. They share a lawful composition shape, not one runtime behavior. The State wrapper itself does not enforce immutability or purity.

**Method names need supporting semantics.** A method called `flatMap` still needs a compatible way to introduce values and behavior that satisfies the laws. Related operations may have different jobs: `await()` obtains a `Deferred` result, while `suspend` marks a function that may suspend.

**Kotlin style remains Kotlin style.** Lambdas may perform side effects regardless of the method that accepts them. Early returns, safe calls, ordinary suspending code, and Arrow's direct style are often clearer than an explicit `flatMap` chain.

### Reading an Unfamiliar API

When an API looks monadic, work through three questions:

1. What is the outer type shape, how does an ordinary value enter it, and how are operations chained?
2. What does the shape mean, and what does chaining preserve, skip, combine, or defer?
3. Do regrouping and identity preserve all observable behavior, including failure, timing, cancellation, state, resources, and side effects?

We can now restate the opening definition more plainly:

> A monad provides a lawful way to put values into a computational structure and chain functions that produce more values in the same structure.

That definition is a tool for reading and designing APIs. It is not a reason to replace clear Kotlin with more abstract code.

## References

- [Kotlin null safety](https://kotlinlang.org/docs/null-safety.html)
- [Kotlin collection `flatMap`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/flat-map.html)
- [Kotlin `Result`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-result/)
- [Kotlin coroutine basics](https://kotlinlang.org/docs/coroutines-basics.html)
- [kotlinx.coroutines `Deferred`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/)
- [Arrow: from other functional-programming languages](https://arrow-kt.io/learn/quickstart/from-fp/)
- [Arrow: receivers and `flatMap`](https://arrow-kt.io/learn/design/receivers-flatmap/)
- [Arrow typed errors](https://arrow-kt.io/learn/typed-errors/)
- [Arrow 2.2.3 API](https://apidocs.arrow-kt.io/)
