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

inclusive-or.mddocs/

Inclusive OR with Ior

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.

Capabilities

Ior Construction

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>

Ior Extension Functions

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))

Ior Type Guards

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(): Boolean

Ior Pattern Matching

Safely 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()  // true

Ior Transformations

Transform 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)

Ior Monadic Operations

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"
// )

Ior Conversions

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)

Ior Combination

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)

Ior Use Cases

Logging and Computation

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)
    }
}

Partial Validation

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)
    }
}

Types

IorNel Type Alias

/**
 * 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

docs

collections.md

error-handling.md

functional-utilities.md

inclusive-or.md

index.md

optional-values.md

raise-dsl.md

tile.json