Functional companion to Kotlin's Standard Library providing type-safe error handling and functional programming constructs for iOS x64 target
—
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.
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>// Create Left (error) and Right (success)
fun <A> A.left(): Either<A, Nothing>
fun <A> A.right(): Either<Nothing, A>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>
}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>
}// 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// 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>// Fold operation
fun <C> fold(ifLeft: (A) -> C, ifRight: (B) -> C): C// Execute side effects
fun onLeft(action: (A) -> Unit): Either<A, B>
fun onRight(action: (B) -> Unit): Either<A, B>// 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>// 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>// Combine two Either values
fun combine(
other: Either<A, B>,
combineLeft: (A, A) -> A,
combineRight: (B, B) -> B
): Either<A, B>// Compare Either values (when components are Comparable)
operator fun <A : Comparable<A>, B : Comparable<B>> compareTo(other: Either<A, B>): Int// 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>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 } // 0import 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")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}")
}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"))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