CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Functional companion to Kotlin's Standard Library providing core data types and error handling

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling with Either

Type-safe error handling that makes failure cases explicit and composable. Either is right-biased, treating success (Right) as the default path while providing extensive combinators for error handling and composition.

Capabilities

Either Construction

Create Either instances representing success or failure states.

/**
 * Create a successful Either containing the given value
 */
fun <B> Either.Companion.Right(value: B): Either<Nothing, B>

/**
 * Create a failed Either containing the given error
 */
fun <A> Either.Companion.Left(value: A): Either<A, Nothing>

/**
 * Execute a function and catch any thrown exceptions
 */
fun <R> Either.Companion.catch(f: () -> R): Either<Throwable, R>

/**
 * Execute a function and catch specific exception types
 */
inline fun <reified T : Throwable, R> Either.Companion.catchOrThrow(
    f: () -> R
): Either<T, R>

Usage Examples:

// Direct construction
val success: Either<String, Int> = Either.Right(42)
val failure: Either<String, Int> = Either.Left("Error occurred")

// Exception handling
val result = Either.catch { "123".toInt() }  // Either<Throwable, Int>
val parsed = Either.catchOrThrow<NumberFormatException, Int> { 
    "abc".toInt() 
}  // Either<NumberFormatException, Int>

Either Extension Functions

Convenience functions for creating Either instances from values.

/**
 * Wrap any value in Either.Right
 */
fun <A> A.right(): Either<Nothing, A>

/**
 * Wrap any value in Either.Left
 */
fun <A> A.left(): Either<A, Nothing>

/**
 * Create an EitherNel with a single error in NonEmptyList
 */
fun <E> E.leftNel(): EitherNel<E, Nothing>

Either Type Guards

Check the state of Either instances with type-safe guards.

/**
 * Check if Either is Left (error case)
 * @return true if Left, false if Right
 */
fun <A, B> Either<A, B>.isLeft(): Boolean

/**
 * Check if Either is Right (success case)  
 * @return true if Right, false if Left
 */
fun <A, B> Either<A, B>.isRight(): Boolean

/**
 * Check if Either is Left and matches predicate
 */
fun <A, B> Either<A, B>.isLeft(predicate: (A) -> Boolean): Boolean

/**
 * Check if Either is Right and matches predicate
 */
fun <A, B> Either<A, B>.isRight(predicate: (B) -> Boolean): Boolean

Either Pattern Matching

Safely extract values or perform operations based on Either state.

/**
 * Pattern match on Either, providing handlers for both cases
 */
fun <A, B, C> Either<A, B>.fold(
    ifLeft: (A) -> C,
    ifRight: (B) -> C
): C

/**
 * Extract the Right value or compute a default from Left
 */
fun <A, B> Either<A, B>.getOrElse(default: (A) -> B): B

/**
 * Extract the Right value or return null
 */
fun <A, B> Either<A, B>.getOrNull(): B?

/**
 * Extract the Left value or return null
 */
fun <A, B> Either<A, B>.leftOrNull(): A?

/**
 * Convert Either to Option, keeping only Right values
 */
fun <A, B> Either<A, B>.getOrNone(): Option<B>

Either Transformations

Transform Either values while preserving the container structure.

/**
 * Transform the Right value if present
 */
fun <A, B, C> Either<A, B>.map(f: (B) -> C): Either<A, C>

/**
 * Transform the Left value if present
 */
fun <A, B, C> Either<A, B>.mapLeft(f: (A) -> C): Either<C, B>

/**
 * Monadic bind - chain operations that can fail
 */
fun <A, B, C> Either<A, B>.flatMap(f: (B) -> Either<A, C>): Either<A, C>

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

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

/**
 * Merge Either<A, A> into A
 */
fun <A> Either<A, A>.merge(): A

Either Side Effects

Perform side effects based on Either state without changing the value.

/**
 * Execute action if Either is Right, return original Either
 */
fun <A, B> Either<A, B>.onRight(action: (B) -> Unit): Either<A, B>

/**
 * Execute action if Either is Left, return original Either
 */
fun <A, B> Either<A, B>.onLeft(action: (A) -> Unit): Either<A, B>

Either Recovery

Recover from errors or handle specific exception types.

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

/**
 * Catch and handle specific exception types
 */
inline fun <E, reified T : Throwable, A> Either<E, A>.catch(
    catch: Raise<E>.(T) -> A
): Either<E, A>

Either Accumulation

Combine multiple Either values, accumulating errors instead of short-circuiting.

/**
 * Combine 2 Either values, accumulating errors with custom combiner
 */
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
    combine: (E, E) -> E,
    a: Either<E, A>,
    b: Either<E, B>,
    transform: (A, B) -> Z
): Either<E, Z>

/**
 * Combine 2 Either values, accumulating errors in NonEmptyList
 */
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
    a: Either<E, A>,
    b: Either<E, B>,
    transform: (A, B) -> Z
): Either<NonEmptyList<E>, Z>

// Similar functions exist for 3-10 parameters

Usage Example:

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 blank".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 >= 0) age.right() else "Age cannot be negative".left()

// Accumulate all validation errors
val userResult = Either.zipOrAccumulate(
    validateName(""),
    validateEmail("invalid-email"),
    validateAge(-5)
) { name, email, age -> User(name, email, age) }
// Result: Either.Left(NonEmptyList("Name cannot be blank", "Invalid email", "Age cannot be negative"))

Either Conversions

Convert Either to other Arrow types.

/**
 * Convert Either to EitherNel
 */
fun <A, B> Either<A, B>.toEitherNel(): EitherNel<A, B>

/**
 * Convert Either to Ior
 */
fun <A, B> Either<A, B>.toIor(): Ior<A, B>

zipOrAccumulate - Error Accumulation

Combine multiple Either values, accumulating errors instead of short-circuiting on the first failure.

/**
 * Combine 2 Either values with custom error combination
 */
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
    combine: (E, E) -> E,
    a: Either<E, A>,
    b: Either<E, B>,
    transform: (A, B) -> Z
): Either<E, Z>

/**
 * Combine 2 Either values accumulating errors in NonEmptyList
 */
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
    a: Either<E, A>,
    b: Either<E, B>,
    transform: (A, B) -> Z
): Either<NonEmptyList<E>, Z>

/**
 * Combine 3 Either values with custom error combination
 */
fun <E, A, B, C, Z> Either.Companion.zipOrAccumulate(
    combine: (E, E) -> E,
    a: Either<E, A>,
    b: Either<E, B>,
    c: Either<E, C>,
    transform: (A, B, C) -> Z
): Either<E, Z>

/**
 * Combine 3 Either values accumulating errors in NonEmptyList
 */
fun <E, A, B, C, Z> Either.Companion.zipOrAccumulate(
    a: Either<E, A>,
    b: Either<E, B>,
    c: Either<E, C>,
    transform: (A, B, C) -> Z
): Either<NonEmptyList<E>, Z>

// Additional overloads available for 4-10 parameters with both custom combine and NonEmptyList accumulation

/**
 * Combine 2 EitherNel values accumulating errors
 */
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
    a: EitherNel<E, A>,
    b: EitherNel<E, B>,
    transform: (A, B) -> Z
): EitherNel<E, Z>

// Additional EitherNel overloads available for 3-10 parameters

Usage Examples:

// Custom error combination
val result1 = Either.zipOrAccumulate(
    { e1, e2 -> "$e1; $e2" },
    parseAge("invalid"),
    parseName(""),
    ::User
) // Either.Left("Invalid age; Name required")

// Error accumulation with NonEmptyList
val result2 = Either.zipOrAccumulate(
    validateEmail("invalid-email"),
    validateAge("-5"),
    validateName("")
) { email, age, name -> User(email, age, name) }
// Either.Left(NonEmptyList("Invalid email", "Age must be positive", "Name required"))

Types

EitherNel Type Alias

/**
 * Either with NonEmptyList for error accumulation
 */
typealias EitherNel<E, A> = Either<NonEmptyList<E>, A>

Install with Tessl CLI

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

docs

collections.md

error-handling.md

functional-utilities.md

inclusive-or.md

index.md

optional-values.md

raise-dsl.md

tile.json