Result Monad — Functional Error Handling
Handle errors functionally with Kotlin Result: map, recover, fold, and chaining fallible operations.
// Using Kotlin's built-in Result type
fun parseInt(s: String): Result<Int> = runCatching { s.toInt() }
fun divide(a: Int, b: Int): Result<Double> = runCatching {
require(b != 0) { "Division by zero" }
a.toDouble() / b
}
// Custom Result with sealed class for more control
sealed class Outcome<out T> {
data class Ok<T>(val value: T) : Outcome<T>()
data class Err(val error: AppError) : Outcome<Nothing>()
fun <R> map(transform: (T) -> R): Outcome<R> = when (this) {
is Ok -> Ok(transform(value))
is Err -> this
}
fun <R> flatMap(transform: (T) -> Outcome<R>): Outcome<R> = when (this) {
is Ok -> transform(value)
is Err -> this
}
fun recover(handler: (AppError) -> T): T = when (this) {
is Ok -> value
is Err -> handler(error)
}
fun <R> fold(onOk: (T) -> R, onErr: (AppError) -> R): R = when (this) {
is Ok -> onOk(value)
is Err -> onErr(error)
}
fun getOrNull(): T? = (this as? Ok)?.value
fun getOrDefault(default: @UnsafeVariance T): T = (this as? Ok)?.value ?: default
fun getOrThrow(): T = when (this) {
is Ok -> value
is Err -> throw error.toException()
}
}
sealed class AppError(val message: String) {
class NotFound(val id: String) : AppError("Not found: $id")
class Validation(val field: String, val reason: String) : AppError("$field: $reason")
class Network(val url: String, cause: String) : AppError("Network error at $url: $cause")
class Unauthorized : AppError("Authentication required")
fun toException() = RuntimeException(message)
}
// Service layer using Outcome
data class User(val id: String, val name: String, val email: String)
object UserRepository {
private val users = mapOf(
"1" to User("1", "Alice", "alice@test.com"),
"2" to User("2", "Bob", "bob@test.com")
)
fun findById(id: String): Outcome<User> =
users[id]?.let { Outcome.Ok(it) } ?: Outcome.Err(AppError.NotFound(id))
fun validate(user: User): Outcome<User> = when {
user.name.isBlank() -> Outcome.Err(AppError.Validation("name", "cannot be blank"))
!user.email.contains("@") -> Outcome.Err(AppError.Validation("email", "invalid format"))
else -> Outcome.Ok(user)
}
}
fun main() {
// Built-in Result
val r1 = parseInt("42")
val r2 = parseInt("abc")
println("Parse 42: ${r1.getOrNull()}") // 42
println("Parse abc: ${r2.getOrNull()}") // null
// Result chaining
val computed = parseInt("10")
.mapCatching { it * 2 }
.mapCatching { it / 0 } // ArithmeticException
.recover { -1 }
println("Computed: $computed") // -1
// fold
val message = parseInt("abc").fold(
onSuccess = { "Got: $it" },
onFailure = { "Error: ${it.message}" }
)
println(message)
// Custom Outcome
println("\n--- Custom Outcome ---")
val user = UserRepository.findById("1")
.flatMap { UserRepository.validate(it) }
.map { it.copy(name = it.name.uppercase()) }
user.fold(
onOk = { println("User: $it") },
onErr = { println("Error: ${it.message}") }
)
// Not found
UserRepository.findById("99").fold(
onOk = { println("Found: $it") },
onErr = { println("Error: ${it.message}") } // Not found: 99
)
// recover with default
val fallback = UserRepository.findById("99")
.recover { User("0", "Guest", "guest@test.com") }
println("Recovered: $fallback")
}Use Cases
- Type-safe error handling without exceptions
- Service layer error propagation
- Chaining fallible operations
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Kotlin Result API Functional Error Handling
Use Kotlin's built-in Result type for functional error handling: runCatching, map, recover, and fold.
Best for: Functional error handling without try-catch
Functional Error Handling with Either
Use Either and Option for type-safe error handling: Railway-oriented programming without exceptions.
Best for: Type-safe error handling without exceptions
Functional Error Handling with Either
Implement Either monad for type-safe error handling with map, flatMap, and fold operations.
Best for: Type-safe error handling without exceptions
Collections — map, filter, groupBy, and More
Master Kotlin collections: functional transformations, aggregations, grouping, and partition operations.
Best for: Data processing and transformation pipelines