Functional companion to Kotlin's Standard Library providing type-safe error handling and functional programming constructs for iOS x64 target
—
The Ior (Inclusive OR) type represents computations that can have both success values and accumulated context/warnings simultaneously. Unlike Either which is exclusive (either error OR success), Ior can represent Left (only error), Right (only success), or Both (error AND success).
sealed class Ior<out A, out B>
data class Left<out A>(val value: A) : Ior<A, Nothing>()
data class Right<out B>(val value: B) : Ior<Nothing, B>()
data class Both<out A, out B>(val leftValue: A, val rightValue: B) : Ior<A, B>()
// Type alias for NonEmptyList context
typealias IorNel<A, B> = Ior<Nel<A>, B>// Create Left, Right, or Both
fun <A> A.leftIor(): Ior<A, Nothing>
fun <A> A.rightIor(): Ior<Nothing, A>
fun <A, B> Pair<A, B>.bothIor(): Ior<A, B>companion object Ior {
// From nullable values - returns null if both are null
fun <A, B> fromNullables(a: A?, b: B?): Ior<A, B>?
// NonEmptyList variants
fun <A, B> leftNel(a: A): IorNel<A, B>
fun <A, B> bothNel(a: A, b: B): IorNel<A, B>
}// Type checking
fun isLeft(): Boolean
fun isRight(): Boolean
fun isBoth(): Boolean
// Predicate-based checking
fun isLeft(predicate: (A) -> Boolean): Boolean
fun isRight(predicate: (B) -> Boolean): Boolean
fun isBoth(leftPredicate: (A) -> Boolean, rightPredicate: (B) -> Boolean): Boolean
// Value extraction
fun getOrNull(): B? // Returns right value or null
fun leftOrNull(): A? // Returns left value or null// Transform right values (preserves left in Both case)
fun <D> map(f: (B) -> D): Ior<A, D>
// Transform left values (preserves right in Both case)
fun <C> mapLeft(fa: (A) -> C): Ior<C, B>
// Monadic bind - requires combine function for left values
fun <D> flatMap(combine: (A, A) -> A, f: (B) -> Ior<A, D>): Ior<A, D>
// Error handling - requires combine function for right values
fun <D> handleErrorWith(combine: (B, B) -> B, f: (A) -> Ior<D, B>): Ior<D, B>
// Swap left and right types
fun swap(): Ior<B, A>// Three-way fold
fun <C> fold(fa: (A) -> C, fb: (B) -> C, fab: (A, B) -> C): C// Convert to Either (ignores left value in Both case)
fun toEither(): Either<A, B>
// Convert to Pair with nullables
fun toPair(): Pair<A?, B?>
// Isomorphic conversion
fun unwrap(): Either<Either<A, B>, Pair<A, B>>
// Extract right value with default for Left
fun getOrElse(default: (A) -> B): B// Combine two Ior values
fun combine(
other: Ior<A, B>,
combineA: (A, A) -> A,
combineB: (B, B) -> B
): Ior<A, B>
// Flatten nested Ior
fun Ior<A, Ior<A, B>>.flatten(combine: (A, A) -> A): Ior<A, B>// Compare Ior values (when components are Comparable)
operator fun <A : Comparable<A>, B : Comparable<B>> compareTo(other: Ior<A, B>): Int// Convert to NonEmptyList context form
fun <A, B> Ior<A, B>.toIorNel(): IorNel<A, B>import arrow.core.*
// Create different Ior variants
val leftOnly = "warning".leftIor<String, Int>() // Left("warning")
val rightOnly = 42.rightIor<String, Int>() // Right(42)
val both = Ior.Both("warning", 42) // Both("warning", 42)
// Transform values
val doubled = rightOnly.map { it * 2 } // Right(84)
val bothDoubled = both.map { it * 2 } // Both("warning", 84)
// Extract values
val result1 = rightOnly.getOrElse { 0 } // 42
val result2 = leftOnly.getOrElse { 0 } // 0
val result3 = both.getOrElse { 0 } // 42 (has right value)import arrow.core.*
// Parsing with warnings
fun parseNumber(input: String): Ior<List<String>, Int> {
val warnings = mutableListOf<String>()
if (input.startsWith("0") && input.length > 1) {
warnings.add("Leading zero detected")
}
return input.toIntOrNull()?.let { number ->
if (warnings.isEmpty()) {
number.rightIor()
} else {
Ior.Both(warnings, number)
}
} ?: "Invalid number format".nel().leftIor()
}
val results = listOf("007", "42", "abc", "0123").map { parseNumber(it) }
// Results:
// Both(["Leading zero detected"], 7)
// Right(42)
// Left(["Invalid number format"])
// Both(["Leading zero detected"], 123)import arrow.core.*
data class ValidationResult(val warnings: List<String>, val errors: List<String>)
fun validateUser(name: String, email: String): Ior<List<String>, User> {
val warnings = mutableListOf<String>()
val errors = mutableListOf<String>()
// Name validation
when {
name.isBlank() -> errors.add("Name is required")
name.length < 2 -> warnings.add("Name is very short")
}
// Email validation
when {
email.isBlank() -> errors.add("Email is required")
!email.contains('@') -> errors.add("Invalid email format")
!email.contains('.') -> warnings.add("Email might be missing domain")
}
return when {
errors.isNotEmpty() -> errors.leftIor()
warnings.isNotEmpty() -> Ior.Both(warnings, User(name, email))
else -> User(name, email).rightIor()
}
}
val valid = validateUser("Alice", "alice@example.com")
// Right(User("Alice", "alice@example.com"))
val warnings = validateUser("Al", "alice@localhost")
// Both(["Name is very short", "Email might be missing domain"], User("Al", "alice@localhost"))
val errors = validateUser("", "invalid")
// Left(["Name is required", "Invalid email format"])import arrow.core.*
fun processData(input: String): Ior<List<String>, ProcessedData> {
return parseInput(input)
.flatMap(::combine) { parsed -> validate(parsed) }
.flatMap(::combine) { validated -> transform(validated) }
}
fun combine(warnings1: List<String>, warnings2: List<String>): List<String> =
warnings1 + warnings2
// Each step can add warnings while still producing a result
fun parseInput(input: String): Ior<List<String>, ParsedData> = TODO()
fun validate(data: ParsedData): Ior<List<String>, ValidatedData> = TODO()
fun transform(data: ValidatedData): Ior<List<String>, ProcessedData> = TODO()import arrow.core.*
fun handleResult(result: Ior<String, Int>): String = result.fold(
fa = { error -> "Error: $error" },
fb = { value -> "Success: $value" },
fab = { error, value -> "Warning: $error, but got result: $value" }
)
val warning = Ior.Both("deprecation warning", 42)
println(handleResult(warning))
// "Warning: deprecation warning, but got result: 42"Install with Tessl CLI
npx tessl i tessl/maven-io-arrow-kt--arrow-core-iosx64