kotlinintermediate

Sealed Classes and Exhaustive when

Model restricted hierarchies with sealed classes: exhaustive when, data objects, and state machines.

kotlin
// API Result type
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String, val code: Int = 500) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

// State machine
sealed interface OrderState {
    data object Pending : OrderState
    data class Processing(val startedAt: Long) : OrderState
    data class Shipped(val trackingId: String) : OrderState
    data class Delivered(val deliveredAt: Long) : OrderState
    data class Cancelled(val reason: String) : OrderState
}

// Expression tree
sealed interface Expr {
    data class Num(val value: Double) : Expr
    data class Add(val left: Expr, val right: Expr) : Expr
    data class Mul(val left: Expr, val right: Expr) : Expr
    data class Neg(val expr: Expr) : Expr
}

fun eval(expr: Expr): Double = when (expr) {
    is Expr.Num -> expr.value
    is Expr.Add -> eval(expr.left) + eval(expr.right)
    is Expr.Mul -> eval(expr.left) * eval(expr.right)
    is Expr.Neg -> -eval(expr.expr)
    // No else needed — exhaustive!
}

fun prettyPrint(expr: Expr): String = when (expr) {
    is Expr.Num -> expr.value.toString()
    is Expr.Add -> "(${prettyPrint(expr.left)} + ${prettyPrint(expr.right)})"
    is Expr.Mul -> "(${prettyPrint(expr.left)} * ${prettyPrint(expr.right)})"
    is Expr.Neg -> "(-${prettyPrint(expr.expr)})"
}

// Process result
fun <T> handleResult(result: Result<T>): String = when (result) {
    is Result.Success -> "Data: ${result.data}"
    is Result.Error -> "Error ${result.code}: ${result.message}"
    Result.Loading -> "Loading..."
}

// State transitions
fun transition(state: OrderState, action: String): OrderState = when (state) {
    is OrderState.Pending -> when (action) {
        "process" -> OrderState.Processing(System.currentTimeMillis())
        "cancel" -> OrderState.Cancelled("Customer request")
        else -> state
    }
    is OrderState.Processing -> when (action) {
        "ship" -> OrderState.Shipped("TRK-${System.currentTimeMillis()}")
        "cancel" -> OrderState.Cancelled("Out of stock")
        else -> state
    }
    is OrderState.Shipped -> when (action) {
        "deliver" -> OrderState.Delivered(System.currentTimeMillis())
        else -> state
    }
    is OrderState.Delivered -> state // terminal
    is OrderState.Cancelled -> state // terminal
}

fun main() {
    // Result
    val result: Result<String> = Result.Success("Hello")
    println(handleResult(result))
    println(handleResult(Result.Error("Not found", 404)))

    // Expression tree
    val expr = Expr.Add(
        Expr.Mul(Expr.Num(2.0), Expr.Num(3.0)),
        Expr.Neg(Expr.Num(1.0))
    )
    println("${prettyPrint(expr)} = ${eval(expr)}") // (2.0 * 3.0) + (-1.0) = 5.0

    // State machine
    var order: OrderState = OrderState.Pending
    order = transition(order, "process")
    order = transition(order, "ship")
    order = transition(order, "deliver")
    println("Order state: $order")
}

Use Cases

  • Type-safe API response handling
  • State machine implementations
  • Expression trees and recursive data structures

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.