Functional companion to Kotlin's Standard Library providing core data types and error handling
—
Represents values that can be either one type, another type, or both simultaneously. Ior (Inclusive OR) is useful for computations that can produce partial results alongside errors or warnings, allowing you to accumulate information from both success and failure paths.
Create Ior instances representing left values, right values, or both.
/**
* Ior containing only a left value (typically error/warning)
*/
data class Left<out A>(val value: A) : Ior<A, Nothing>
/**
* Ior containing only a right value (typically success)
*/
data class Right<out B>(val value: B) : Ior<Nothing, B>
/**
* Ior containing both left and right values
*/
data class Both<out A, out B>(val left: A, val right: B) : Ior<A, B>Convenience functions for creating Ior instances.
/**
* Wrap any value in Ior.Left
*/
fun <A> A.leftIor(): Ior<A, Nothing>
/**
* Wrap any value in Ior.Right
*/
fun <A> A.rightIor(): Ior<Nothing, A>
/**
* Create Ior.Both from two values
*/
fun <A, B> bothIor(left: A, right: B): Ior<A, B>Usage Examples:
// Direct construction
val error: Ior<String, Int> = Ior.Left("Error occurred")
val success: Ior<String, Int> = Ior.Right(42)
val partial: Ior<String, Int> = Ior.Both("Warning: partial result", 42)
// Using extension functions
val warning = "Deprecated API".leftIor()
val result = 100.rightIor()
val combined = bothIor("Processing with warnings", listOf(1, 2, 3))Check the state of Ior instances with type-safe guards.
/**
* Check if Ior contains only left value
* @return true if Left, false otherwise
*/
fun <A, B> Ior<A, B>.isLeft(): Boolean
/**
* Check if Ior contains only right value
* @return true if Right, false otherwise
*/
fun <A, B> Ior<A, B>.isRight(): Boolean
/**
* Check if Ior contains both left and right values
* @return true if Both, false otherwise
*/
fun <A, B> Ior<A, B>.isBoth(): BooleanSafely extract values or perform operations based on Ior state.
/**
* Pattern match on Ior, providing handlers for all three cases
*/
fun <A, B, C> Ior<A, B>.fold(
ifLeft: (A) -> C,
ifRight: (B) -> C,
ifBoth: (A, B) -> C
): C
/**
* Extract values into a pair of optionals
*/
fun <A, B> Ior<A, B>.pad(): Pair<A?, B?>
/**
* Unwrap into Either<Either<A, B>, Pair<A, B>>
*/
fun <A, B> Ior<A, B>.unwrap(): Either<Either<A, B>, Pair<A, B>>Usage Examples:
val result: Ior<String, Int> = Ior.Both("Warning", 42)
// Pattern matching
val message = result.fold(
ifLeft = { error -> "Error: $error" },
ifRight = { value -> "Success: $value" },
ifBoth = { warning, value -> "Warning: $warning, Result: $value" }
)
// Extract as optional pair
val (maybeWarning, maybeResult) = result.pad() // ("Warning", 42)
// Check state
val hasBoth = result.isBoth() // true
val hasWarning = result.isLeft() || result.isBoth() // trueTransform Ior values while preserving the container structure.
/**
* Transform the right value if present
*/
fun <A, B, C> Ior<A, B>.map(f: (B) -> C): Ior<A, C>
/**
* Transform the left value if present
*/
fun <A, B, C> Ior<A, B>.mapLeft(f: (A) -> C): Ior<C, B>
/**
* Transform both values if both are present
*/
fun <A, B, C, D> Ior<A, B>.bimap(
leftMap: (A) -> C,
rightMap: (B) -> D
): Ior<C, D>
/**
* Swap left and right positions
*/
fun <A, B> Ior<A, B>.swap(): Ior<B, A>Usage Examples:
val processing: Ior<List<String>, List<Int>> = Ior.Both(
listOf("Warning: deprecated field"),
listOf(1, 2, 3)
)
// Transform right values
val doubled = processing.map { numbers -> numbers.map { it * 2 } }
// Result: Ior.Both(["Warning: deprecated field"], [2, 4, 6])
// Transform left values
val formatted = processing.mapLeft { warnings ->
warnings.map { "⚠️ $it" }
}
// Transform both sides
val processed = processing.bimap(
leftMap = { warnings -> warnings.size },
rightMap = { numbers -> numbers.sum() }
)
// Result: Ior.Both(1, 6)Chain Ior operations with proper left value combination.
/**
* Monadic bind with left value combination
*/
fun <A, B, C> Ior<A, B>.flatMap(
combine: (A, A) -> A,
f: (B) -> Ior<A, C>
): Ior<A, C>Usage Examples:
fun processStep1(input: String): Ior<List<String>, Int> {
val warnings = mutableListOf<String>()
val result = input.length
if (input.contains("deprecated")) {
warnings.add("Using deprecated input format")
}
return if (warnings.isEmpty()) {
Ior.Right(result)
} else {
Ior.Both(warnings, result)
}
}
fun processStep2(value: Int): Ior<List<String>, String> {
val warnings = mutableListOf<String>()
val result = "Processed: $value"
if (value > 100) {
warnings.add("Large value detected")
}
return if (warnings.isEmpty()) {
Ior.Right(result)
} else {
Ior.Both(warnings, result)
}
}
// Chain operations, combining warnings
val pipeline = processStep1("deprecated_input_with_long_name")
.flatMap(
combine = { w1, w2 -> w1 + w2 } // Combine warning lists
) { value -> processStep2(value) }
// Result: Ior.Both(
// ["Using deprecated input format", "Large value detected"],
// "Processed: 26"
// )Convert Ior to other Arrow types.
/**
* Convert Ior to Either, losing Both case (Right wins)
*/
fun <A, B> Ior<A, B>.toEither(): Either<A, B>
/**
* Convert Ior to Option, keeping only Right values
*/
fun <A, B> Ior<A, B>.toOption(): Option<B>
/**
* Convert Ior to nullable, keeping only Right values
*/
fun <A, B> Ior<A, B>.orNull(): B?Usage Examples:
val both: Ior<String, Int> = Ior.Both("warning", 42)
val left: Ior<String, Int> = Ior.Left("error")
val right: Ior<String, Int> = Ior.Right(100)
// Convert to Either (Right wins in Both case)
val eitherFromBoth = both.toEither() // Either.Right(42)
val eitherFromLeft = left.toEither() // Either.Left("error")
val eitherFromRight = right.toEither() // Either.Right(100)
// Convert to Option (only Right values)
val optionFromBoth = both.toOption() // Some(42)
val optionFromLeft = left.toOption() // None
val optionFromRight = right.toOption() // Some(100)Combine multiple Ior values with left value accumulation.
/**
* Zip two Ior values with left combination function
*/
fun <A, B, C, D> Ior<A, B>.zip(
combine: (A, A) -> A,
other: Ior<A, C>,
f: (B, C) -> D
): Ior<A, D>
/**
* Apply function if both sides have right values
*/
fun <A, B, C> Ior<A, B>.ap(
combine: (A, A) -> A,
f: Ior<A, (B) -> C>
): Ior<A, C>Usage Examples:
val result1: Ior<List<String>, Int> = Ior.Both(listOf("warning1"), 10)
val result2: Ior<List<String>, Int> = Ior.Both(listOf("warning2"), 20)
// Combine with warning accumulation
val combined = result1.zip(
combine = { w1, w2 -> w1 + w2 }, // Combine warning lists
other = result2
) { a, b -> a + b }
// Result: Ior.Both(["warning1", "warning2"], 30)Accumulate logs/warnings while computing results.
fun processData(data: List<String>): Ior<List<String>, List<Int>> {
val warnings = mutableListOf<String>()
val results = mutableListOf<Int>()
for (item in data) {
when {
item.isBlank() -> warnings.add("Empty item skipped")
item.startsWith("old_") -> {
warnings.add("Legacy format detected: $item")
results.add(item.removePrefix("old_").length)
}
else -> results.add(item.length)
}
}
return when {
warnings.isEmpty() -> Ior.Right(results)
results.isEmpty() -> Ior.Left(warnings)
else -> Ior.Both(warnings, results)
}
}Validate data while collecting all validation errors.
data class ValidationResult<A>(val warnings: List<String>, val value: A)
fun validateUser(input: UserInput): Ior<List<String>, User> {
val warnings = mutableListOf<String>()
val name = if (input.name.isNotBlank()) {
input.name
} else {
warnings.add("Name should not be empty")
"Unknown"
}
val email = if (input.email.contains("@")) {
input.email
} else {
warnings.add("Invalid email format")
"unknown@example.com"
}
val user = User(name, email)
return if (warnings.isEmpty()) {
Ior.Right(user)
} else {
Ior.Both(warnings, user)
}
}/**
* Ior with NonEmptyList for error accumulation
*/
typealias IorNel<E, A> = Ior<NonEmptyList<E>, A>Install with Tessl CLI
npx tessl i tessl/maven-io-arrow-kt--arrow-core