CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-arrow-kt--arrow-core-iosx64

Functional companion to Kotlin's Standard Library providing type-safe error handling and functional programming constructs for iOS x64 target

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling

The Either type provides typed error handling as an alternative to exceptions. It represents computations that can either succeed with a value of type B or fail with an error of type A. Either is right-biased, meaning operations like map and flatMap work on the success (Right) case.

Core Types

sealed class Either<out A, out B>

data class Left<out A>(val value: A) : Either<A, Nothing>()

data class Right<out B>(val value: B) : Either<Nothing, B>()

// Type alias for error accumulation
typealias EitherNel<E, A> = Either<NonEmptyList<E>, A>

Construction

Direct Construction

// Create Left (error) and Right (success)
fun <A> A.left(): Either<A, Nothing>
fun <A> A.right(): Either<Nothing, A>

Safe Exception Handling

companion object Either {
    // Catch all exceptions
    fun <R> catch(f: () -> R): Either<Throwable, R>
    
    // Catch specific exception types
    fun <T : Throwable, R> catchOrThrow(f: () -> R): Either<T, R>
}

Error Accumulation

companion object Either {
    // Combine multiple Either values, accumulating errors
    fun <E, A, B, Z> zipOrAccumulate(
        combine: (E, E) -> E,
        a: Either<E, A>,
        b: Either<E, B>,
        transform: (A, B) -> Z
    ): Either<E, Z>
    
    // Non-empty list error accumulation (2-10 arity overloads)
    fun <E, A, B, Z> zipOrAccumulate(
        a: Either<E, A>,
        b: Either<E, B>,
        transform: (A, B) -> Z
    ): Either<NonEmptyList<E>, Z>
}

Inspection

// Type checking
fun isLeft(): Boolean
fun isRight(): Boolean
fun isLeft(predicate: (A) -> Boolean): Boolean
fun isRight(predicate: (B) -> Boolean): Boolean

// Value extraction
fun getOrNull(): B?
fun leftOrNull(): A?
fun getOrElse(default: (A) -> B): B

Transformation

// Transform Right values
fun <C> map(f: (B) -> C): Either<A, C>
fun <C> flatMap(f: (B) -> Either<A, C>): Either<A, C>

// Transform Left values  
fun <C> mapLeft(f: (A) -> C): Either<C, B>
fun <C> handleErrorWith(f: (A) -> Either<C, B>): Either<C, B>

// Swap Left and Right
fun swap(): Either<B, A>

Pattern Matching

// Fold operation
fun <C> fold(ifLeft: (A) -> C, ifRight: (B) -> C): C

Side Effects

// Execute side effects
fun onLeft(action: (A) -> Unit): Either<A, B>
fun onRight(action: (B) -> Unit): Either<A, B>

Conversion

// Convert to other types
fun toIor(): Ior<A, B>
fun getOrNone(): Option<B>

// Merge when both types are the same
fun Either<A, A>.merge(): A

// Flatten nested Either
fun Either<A, Either<A, B>>.flatten(): Either<A, B>

Error Recovery

// Recover using Raise DSL
fun <EE> recover(recover: Raise<EE>.(A) -> B): Either<EE, B>

// Catch specific exceptions with Raise DSL
fun <E, T : Throwable> catch(catch: Raise<E>.(T) -> A): Either<E, A>

Combining Either Values

// Combine two Either values
fun combine(
    other: Either<A, B>, 
    combineLeft: (A, A) -> A, 
    combineRight: (B, B) -> B
): Either<A, B>

Comparison

// Compare Either values (when components are Comparable)
operator fun <A : Comparable<A>, B : Comparable<B>> compareTo(other: Either<A, B>): Int

NonEmptyList Error Handling

// Convert to NonEmptyList error form
fun <E, A> Either<E, A>.toEitherNel(): EitherNel<E, A>

// Create Left with NonEmptyList
fun <E> E.leftNel(): EitherNel<E, Nothing>

Usage Examples

Basic Error Handling

import arrow.core.*

// Safe division
fun divide(x: Int, y: Int): Either<String, Int> =
    if (y == 0) "Division by zero".left()
    else (x / y).right()

val success = divide(10, 2) // Right(5)
val error = divide(10, 0)   // Left("Division by zero")

// Extract values
val result1 = success.getOrElse { 0 } // 5
val result2 = error.getOrElse { 0 }   // 0

Chaining Operations

import arrow.core.*

fun parseAge(input: String): Either<String, Int> =
    input.toIntOrNull()?.right() ?: "Invalid number".left()

fun validateAge(age: Int): Either<String, Int> =
    if (age >= 0) age.right() else "Negative age".left()

fun processAge(input: String): Either<String, String> =
    parseAge(input)
        .flatMap { validateAge(it) }
        .map { "Age: $it years" }

val valid = processAge("25")   // Right("Age: 25 years")
val invalid = processAge("-5") // Left("Negative age")

Exception Handling

import arrow.core.*

// Catch exceptions safely
val result: Either<Throwable, String> = Either.catch {
    "Hello World".substring(20) // Throws StringIndexOutOfBoundsException
}

when (result) {
    is Either.Left -> println("Error: ${result.value.message}")
    is Either.Right -> println("Success: ${result.value}")
}

Error Accumulation

import arrow.core.*

data class User(val name: String, val email: String, val age: Int)

fun validateName(name: String): Either<String, String> =
    if (name.isNotBlank()) name.right() else "Name cannot be empty".left()

fun validateEmail(email: String): Either<String, String> =
    if (email.contains('@')) email.right() else "Invalid email".left()

fun validateAge(age: Int): Either<String, Int> =
    if (age >= 18) age.right() else "Must be 18 or older".left()

// Accumulate all errors
fun validateUser(name: String, email: String, age: Int): Either<NonEmptyList<String>, User> =
    Either.zipOrAccumulate(
        validateName(name),
        validateEmail(email),
        validateAge(age)
    ) { validName, validEmail, validAge ->
        User(validName, validEmail, validAge)
    }

val validUser = validateUser("Alice", "alice@example.com", 25)
// Right(User("Alice", "alice@example.com", 25))

val invalidUser = validateUser("", "invalid-email", 16)
// Left(NonEmptyList("Name cannot be empty", "Invalid email", "Must be 18 or older"))

Integration with Raise DSL

import arrow.core.*
import arrow.core.raise.*

fun Raise<String>.processUser(name: String, age: Int): User {
    ensure(name.isNotBlank()) { "Name cannot be empty" }
    ensure(age >= 18) { "Must be 18 or older" }
    return User(name, "", age)
}

val result = either {
    processUser("Alice", 25)
}

// Pattern matching
val message = result.fold(
    ifLeft = { error -> "Error: $error" },
    ifRight = { user -> "Created user: ${user.name}" }
)

Install with Tessl CLI

npx tessl i tessl/maven-io-arrow-kt--arrow-core-iosx64

docs

collection-extensions.md

error-handling.md

index.md

optional-values.md

partial-results.md

product-types.md

raise-dsl.md

safe-collections.md

utility-functions.md

tile.json