Skip to content

Explicit success/failure modeling for Kotlin using a Result monad. Favors explicit control flow and predictable error handling over exceptions.

License

Notifications You must be signed in to change notification settings

erwin-kok/result-monad

Repository files navigation

Result Monad

ci Maven Central Kotlin License

Introduction

Consider the following function signature:

fun createItem(name: String): String {
    ...
} 

While simple, this signature communicates only the success path. It provides no information about possible failure modes, nor whether failure is expressed via return values or exceptions.

Documenting error conditions in comments is brittle: comments drift, but types do not. For long-lived codebases, the type system should describe both success and failure.

This library provides a small, explicit Result type to model this directly.

Motivation

Exception-based control flow is implicit and non-local. Once execution enters a try block, it becomes unclear which operation caused control to jump to a catch clause.

try {
    doSomething()
    doSomethingElse()
    finalizeTheThing()
    doSomeCleanup()
} catch (e: IllegalArgumentException) {
    ...
}

In this example, it is impossible to determine:

  • Which call failed
  • Which resources were successfully acquired
  • Which cleanup steps are still required

This complicates reasoning about control flow and resource lifetime.

An explicit Result type keeps failure local and visible.

Result Monad

In its generic form, a result monad represents either:

  • Ok(value) on success
  • Err(error) on failure

Example:

fun divideNumbers(a: Float, b: Float): Result<Float> {
    if (b == 0) {
        return Err("Division by zero is not allowed")
    }
    return Ok(a / b)
}

The caller is required to handle the result explicitly:

val result = divideNumber(n1, n2)
    .getOrElse {
        // Handle division by zero
        log.error { "Division by zero: ${it.message}" }
        return Err(it) // Return the error to its caller.
    }
log.info { "n1 divided by n2 is $result" }

Failure is now part of the normal control flow, not an exceptional side-channel.

Chaining calls

Because Result distinguishes success and failure, operations can be composed safely.

Functions such as map apply transformations only when a value is present:

val result = divideNumber(n1, n2)
    .map { it * 2 }

If divideNumbers returns Err, the transformation is skipped and the error is propagated unchanged.

This enables linear, readable pipelines without nested conditionals or exception handling.

Installation

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.erwinkok.result:result-monad:$latest")
}

Design Choices

Fixed Error Type

Unlike many Result<T, E> implementations, this library uses a fixed error type:

fun doSomething(): Result<String>

This reduces verbosity and keeps APIs compact. Error is a lightweight wrapper around a message and is itself an Exception. It can be created from either a string or an existing exception:

return Err("Invalid argument")


return Err(IllegalArgumentException()())

Trade-off

Using a fixed error type means the original exception type is not preserved. When error identity matters, a stable error instance can be used:

val illegalArgumentError = Error("Illegal argument")
val result = doSomething()
    .getOrElse {
        if (it === illegalArgumentError) {
            // handle illegal argument
        } else {
            // handle other errors
        }
    }

This design favors:

  • Explicit control flow
  • Simpler APIs
  • Reduced generic noise

at the cost of typed error hierarchies.

Prior Art

The concept of a result type is well established in languages such as Go and Rust.

In Kotlin, this library is inspired by Michael Bull’s excellent kotlin-result.

The primary difference is the use of a fixed error type, trading flexibility for simplicity and consistency.

License

This project is licensed under the BSD-3-Clause license, see LICENSE file for more details.

About

Explicit success/failure modeling for Kotlin using a Result monad. Favors explicit control flow and predictable error handling over exceptions.

Topics

Resources

License

Stars

Watchers

Forks

Languages