CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Functional companion to Kotlin's Standard Library - Core module with iosArm64 target support

Pending
Overview
Eval results
Files

raise-dsl.mddocs/

Raise DSL

Composable typed error handling system with short-circuiting and error accumulation capabilities. The Raise DSL provides a unified approach to error handling that integrates seamlessly with Arrow's data types.

Capabilities

Raise<Error>

Core interface for typed error handling that allows raising errors and binding over various error-containing types.

/**
 * Core interface for typed error handling
 * @param Error The type of errors that can be raised
 */
@RaiseDSL
interface Raise<in Error> {
    /**
     * Raise an error, short-circuiting the computation
     * @param error The error to raise
     * @return Nothing (never returns)
     */
    fun raise(error: Error): Nothing
    
    /**
     * Extract the success value from Either, or raise the error
     * @param receiver Either to bind over
     * @return Success value if Right, raises error if Left
     */
    fun <A> Either<Error, A>.bind(): A
    
    /**
     * Extract value from Option, raising Unit if None (requires SingletonRaise)
     * @param receiver Option to bind over
     * @return Value if Some, raises if None
     */
    fun <A> Option<A>.bind(): A  // Available in SingletonRaise context
    
    /**
     * Extract success value from Ior, accumulating any left values
     * @param receiver Ior to bind over
     * @return Right or Both right value, accumulates left values
     */
    fun <A> Ior<Error, A>.bind(): A  // Available in IorRaise context
    
    /**
     * Bind over all Either values in a collection
     * @param receiver Collection of Either values
     * @return Collection of success values, raises first error encountered
     */
    fun <A> Iterable<Either<Error, A>>.bindAll(): List<A>
    fun <A> NonEmptyList<Either<Error, A>>.bindAll(): NonEmptyList<A>
    fun <A> NonEmptySet<Either<Error, A>>.bindAll(): NonEmptySet<A>
    fun <K, A> Map<K, Either<Error, A>>.bindAll(): Map<K, A>
}

Usage Examples:

import arrow.core.raise.*
import arrow.core.*

// Basic error raising
fun Raise<String>.parsePositiveInt(input: String): Int {
    val number = input.toIntOrNull() ?: raise("Not a valid integer: $input")
    if (number <= 0) raise("Number must be positive: $number")
    return number
}

// Using Either DSL
val result = either {
    val x = parsePositiveInt("42")  // 42
    val y = parsePositiveInt("0")   // Raises "Number must be positive: 0"
    x + y  // This line never executes
}  // Left("Number must be positive: 0")

// Binding over Either values
val computation = either {
    val a = Either.right(10).bind()     // 10
    val b = Either.right(20).bind()     // 20
    val c = Either.left("error").bind() // Raises "error"
    a + b + c  // Never executes
}  // Left("error")

SingletonRaise<Error>

Specialized Raise implementation for handling nullable values and Option types.

/**
 * Specialized Raise for unit-based errors, typically for null handling
 * @param Error Error type (often Unit for simple null checks)
 */
class SingletonRaise<in Error>(private val raise: Raise<Unit>) : Raise<Error> {
    /**
     * Ensure a condition is true, raising unit error if false
     * @param condition Boolean condition to check
     */
    fun ensure(condition: Boolean): Unit
    
    /**
     * Ensure a value is not null, raising unit error if null
     * @param value Nullable value to check
     * @return Non-null value
     */
    fun <A> ensureNotNull(value: A?): A
    
    /**
     * Bind over nullable values
     * @param receiver Nullable value
     * @return Non-null value or raises
     */
    fun <A> A?.bind(): A
    
    /**
     * Bind over Option values
     * @param receiver Option to bind over
     * @return Value if Some, raises if None
     */
    fun <A> Option<A>.bind(): A
    
    /**
     * Bind over collections of nullable values
     */
    fun <A> Iterable<A?>.bindAll(): List<A>
    fun <A> NonEmptyList<A?>.bindAll(): NonEmptyList<A>
    fun <K, A> Map<K, A?>.bindAll(): Map<K, A>
    
    /**
     * Recover from errors with fallback computation
     */
    inline fun <A> recover(
        block: SingletonRaise<Error>.() -> A,
        fallback: () -> A
    ): A
}

Usage Examples:

import arrow.core.raise.*
import arrow.core.*

// Working with nullable values
val result = option {
    val name = "Alice".takeIf { it.isNotEmpty() }.bind()  // "Alice"
    val age = "25".toIntOrNull().bind()                   // 25
    val email = null.bind()  // Raises, short-circuits
    "$name ($age) - $email"  // Never executes
}  // None

// Validation with ensure
val validation = option {
    val input = "42"
    ensure(input.isNotBlank()) { }  // Passes
    val number = input.toIntOrNull().bind()
    ensureNotNull(number.takeIf { it > 0 })  // 42
}  // Some(42)

// Binding collections
val numbers = option {
    listOf("1", "2", null, "4").mapNotNull { it?.toIntOrNull() }.bind()  // Fails on null
}  // None

IorRaise<Error>

Raise implementation that accumulates errors while preserving successful computations.

/**
 * Raise implementation for error accumulation with Ior
 * @param Error Error type that can be combined
 */
class IorRaise<Error>(
    val combineError: (Error, Error) -> Error,
    private val combineRef: Atomic<Any?>,
    private val raise: Raise<Error>
) : Raise<Error> {
    /**
     * Accumulate an error without short-circuiting
     * @param error Error to accumulate
     */
    fun accumulate(error: Error): Unit
    
    /**
     * Combine an error with any previously accumulated errors
     * @param error Error to combine
     * @return Combined error
     */
    fun combine(error: Error): Error
    
    /**
     * Bind over Ior values, accumulating left values
     * @param receiver Ior to bind over
     * @return Right value, accumulates any left values
     */
    fun <A> Ior<Error, A>.bind(): A
    
    /**
     * Get value from Either or accumulate the error and provide fallback
     * @param receiver Either to process
     * @param fallback Function to provide fallback value
     * @return Right value or result of fallback after accumulating error
     */
    fun <A> Either<Error, A>.getOrAccumulate(fallback: (Error) -> A): A
    
    /**
     * Recover from accumulated errors
     */
    inline fun <A> recover(
        block: IorRaise<Error>.() -> A,
        fallback: (Error) -> A
    ): A
}

Usage Examples:

import arrow.core.raise.*
import arrow.core.*

// Error accumulation with IorRaise
data class ValidationError(val message: String)

fun combineErrors(e1: ValidationError, e2: ValidationError): ValidationError =
    ValidationError("${e1.message}; ${e2.message}")

val result = ior(::combineErrors) {
    val name = validateName("").getOrAccumulate { ValidationError("Name is required") }
    val email = validateEmail("invalid").getOrAccumulate { ValidationError("Invalid email") }
    val age = validateAge(-1).getOrAccumulate { ValidationError("Age must be positive") }
    
    User(name, email, age)  // Creates user even with accumulated errors
}  // Ior.Both(ValidationError("Name is required; Invalid email; Age must be positive"), User(...))

DSL Builder Functions

Convenient functions for creating computations in the Raise context.

/**
 * Create an Either computation with error handling
 * @param block Computation that can raise errors of type Error
 * @return Either.Left with error or Either.Right with result
 */
inline fun <Error, A> either(block: Raise<Error>.() -> A): Either<Error, A>

/**
 * Create an Option computation with null handling
 * @param block Computation that can fail (raise Unit)
 * @return Some with result or None if computation failed
 */
inline fun <A> option(block: SingletonRaise<Unit>.() -> A): Option<A>

/**
 * Create an Ior computation with error accumulation
 * @param combineError Function to combine multiple errors
 * @param block Computation that can raise and accumulate errors
 * @return Ior with accumulated errors and/or result
 */
inline fun <Error, A> ior(
    combineError: (Error, Error) -> Error,
    block: IorRaise<Error>.() -> A
): Ior<Error, A>

/**
 * Create a nullable computation
 * @param block Computation that can fail
 * @return Result value or null if computation failed
 */
inline fun <A> nullable(block: SingletonRaise<Unit>.() -> A): A?

/**
 * Recover from errors in Either computation
 * @param block Computation that might fail
 * @param fallback Function to handle errors
 * @return Result of computation or fallback
 */
inline fun <Error, A> recover(
    block: Raise<Error>.() -> A,
    fallback: (Error) -> A
): A

/**
 * Fold over Either result with error and success handlers
 * @param block Computation that might fail
 * @param onError Handler for error case
 * @param onSuccess Handler for success case
 * @return Result of appropriate handler
 */
inline fun <Error, A, B> fold(
    block: Raise<Error>.() -> A,
    onError: (Error) -> B,
    onSuccess: (A) -> B
): B

Usage Examples:

import arrow.core.raise.*
import arrow.core.*

// Either computation
val parseResult = either {
    val input = "not-a-number"
    input.toIntOrNull() ?: raise("Invalid number: $input")
}  // Left("Invalid number: not-a-number")

// Option computation with nullable handling
val safeComputation = option {
    val user = findUser("123").bind()     // Returns null if not found
    val profile = user.profile.bind()     // Returns null if no profile
    profile.displayName.bind()            // Returns null if no display name
}  // None if any step returns null

// Recovery with fallback
val withFallback = recover(
    { parsePositiveInt("-5") }  // Raises error
) { error -> 0 }  // 0

// Folding over computation
val message = fold(
    { parsePositiveInt("42") },
    onError = { "Error: $it" },
    onSuccess = { "Success: $it" }
)  // "Success: 42"

Effect System

Suspended computations that can raise typed errors, providing a foundation for asynchronous and concurrent programming with proper error handling.

/**
 * Suspended computation that can raise typed errors
 * @param Error The type of errors that can be raised
 * @param A The type of successful result
 */
typealias Effect<Error, A> = suspend Raise<Error>.() -> A

/**
 * Non-suspended computation that can raise typed errors
 * @param Error The type of errors that can be raised  
 * @param A The type of successful result
 */
typealias EagerEffect<Error, A> = Raise<Error>.() -> A

/**
 * Execute a suspended Effect computation
 * @param block The Effect computation to execute
 * @return Either containing the error or successful result
 */
suspend fun <Error, A> effect(block: suspend Raise<Error>.() -> A): Either<Error, A>

/**
 * Execute an eager Effect computation
 * @param block The EagerEffect computation to execute
 * @return Either containing the error or successful result
 */
fun <Error, A> eagerEffect(block: Raise<Error>.() -> A): Either<Error, A>

/**
 * Execute an Effect and fold over the result
 * @param block The Effect computation to execute
 * @param onError Function to handle error case
 * @param onSuccess Function to handle success case
 */
suspend fun <Error, A, B> Effect<Error, A>.fold(
    onError: (Error) -> B,
    onSuccess: (A) -> B
): B

/**
 * Map over the success value of an Effect
 * @param transform Function to transform the success value
 */
suspend fun <Error, A, B> Effect<Error, A>.map(
    transform: suspend (A) -> B
): Effect<Error, B>

/**
 * Bind two Effects together
 * @param transform Function that takes success value and returns new Effect
 */
suspend fun <Error, A, B> Effect<Error, A>.flatMap(
    transform: suspend (A) -> Effect<Error, B>
): Effect<Error, B>

/**
 * Handle errors in an Effect computation
 * @param recover Function to recover from errors
 */
suspend fun <Error, A, E2> Effect<Error, A>.handleErrorWith(
    recover: suspend (Error) -> Effect<E2, A>
): Effect<E2, A>

/**
 * Get the result of an Effect, returning null if it fails
 * @return The success value or null if error was raised
 */
suspend fun <Error, A> Effect<Error, A>.getOrNull(): A?

/**
 * Merge Effect where Error and A are the same type
 * @return The value, whether it was error or success
 */
suspend fun <A> Effect<A, A>.merge(): A

/**
 * Execute multiple Effects in parallel, accumulating results
 * @param effects Collection of Effects to execute
 * @return Effect containing list of all results
 */
suspend fun <Error, A> Collection<Effect<Error, A>>.parZip(): Effect<Error, List<A>>

Usage Examples:

import arrow.core.raise.*
import arrow.core.*
import kotlinx.coroutines.*

// Suspended Effect computation
suspend fun fetchUserProfile(userId: String): Effect<String, UserProfile> = effect {
    val user = fetchUser(userId) ?: raise("User not found: $userId")
    val profile = fetchProfile(user.id) ?: raise("Profile not found for user: $userId")
    
    UserProfile(user.name, profile.bio, profile.avatar)
}

// Using Effect with error handling
suspend fun handleUserRequest(userId: String) {
    fetchUserProfile(userId).fold(
        onError = { error -> 
            println("Failed to fetch profile: $error")
            respondWithError(error)
        },
        onSuccess = { profile ->
            println("Successfully fetched profile for ${profile.name}")
            respondWithProfile(profile)
        }
    )
}

// Chaining Effects
suspend fun processUserData(userId: String): Effect<String, ProcessedData> = effect {
    val profile = fetchUserProfile(userId).bind()
    val settings = fetchUserSettings(userId).bind()
    val preferences = fetchUserPreferences(userId).bind()
    
    ProcessedData(profile, settings, preferences)
}

// Parallel execution
suspend fun fetchAllData(userIds: List<String>): Effect<String, List<UserProfile>> = effect {
    userIds.map { userId -> 
        fetchUserProfile(userId)
    }.parZip().bind()
}

// Error recovery
suspend fun fetchWithFallback(userId: String): Effect<String, UserProfile> = effect {
    fetchUserProfile(userId)
        .handleErrorWith { error ->
            if (error.contains("not found")) {
                effect { getDefaultProfile() }
            } else {
                effect { raise(error) }
            }
        }
        .bind()
}

RaiseAccumulate System

Advanced error accumulation capabilities for collecting multiple errors while continuing computation.

/**
 * Raise context for accumulating errors with NonEmptyList
 * @param Error The type of errors to accumulate
 */
class RaiseAccumulate<Error> : Raise<NonEmptyList<Error>> {
    /**
     * Map over a collection, accumulating errors
     * @param transform Function that can raise errors
     * @return Collection of results or accumulated errors
     */
    fun <A, B> Iterable<A>.mapOrAccumulate(
        transform: Raise<Error>.(A) -> B
    ): List<B>
    
    /**
     * Zip multiple values, accumulating errors
     * @param action Function combining all values
     * @return Combined result or accumulated errors
     */
    fun <A, B, C> zipOrAccumulate(
        first: Raise<Error>.() -> A,
        second: Raise<Error>.() -> B,
        action: (A, B) -> C
    ): C
    
    // Additional zipOrAccumulate overloads for 3-10 parameters...
}

/**
 * Execute computation with error accumulation
 * @param block Computation that can accumulate errors
 * @return Either with accumulated errors or success
 */
fun <Error, A> mapOrAccumulate(
    block: RaiseAccumulate<Error>.() -> A
): Either<NonEmptyList<Error>, A>

Usage Examples:

import arrow.core.raise.*
import arrow.core.*

data class ValidationError(val field: String, val message: String)

fun validateUser(userData: UserData): Either<NonEmptyList<ValidationError>, ValidUser> =
    mapOrAccumulate {
        zipOrAccumulate(
            { validateName(userData.name) },
            { validateEmail(userData.email) },
            { validateAge(userData.age) }
        ) { name, email, age ->
            ValidUser(name, email, age)
        }
    }

fun validateName(name: String): Raise<ValidationError>.() -> String = {
    if (name.isBlank()) raise(ValidationError("name", "Name cannot be blank"))
    if (name.length < 2) raise(ValidationError("name", "Name too short"))
    name
}

// Processing multiple items with accumulation
fun validateAllUsers(users: List<UserData>): Either<NonEmptyList<ValidationError>, List<ValidUser>> =
    mapOrAccumulate {
        users.mapOrAccumulate { userData ->
            validateUser(userData).bind()
        }
    }

Utility Functions

Additional utility functions for advanced error handling patterns.

/**
 * Ensure a condition is true, otherwise raise an error
 * @param condition Boolean condition to check
 * @param raise Function to produce error if condition is false
 */
fun <Error> Raise<Error>.ensure(condition: Boolean, raise: () -> Error): Unit

/**
 * Ensure a value is not null, otherwise raise an error
 * @param value Nullable value to check
 * @param raise Function to produce error if value is null
 * @return Non-null value
 */
fun <Error, A : Any> Raise<Error>.ensureNotNull(value: A?, raise: () -> Error): A

/**
 * Transform errors before raising them
 * @param transform Function to transform errors
 * @param block Computation that can raise transformed errors
 */
inline fun <Error, A, TransformedError> withError(
    transform: (Error) -> TransformedError,
    block: Raise<Error>.() -> A
): Raise<TransformedError>.() -> A

/**
 * Catch exceptions and convert to raised errors
 * @param transform Function to convert exception to error
 * @param block Computation that might throw exceptions
 * @return Result or raised error
 */
inline fun <Error, A> Raise<Error>.catch(
    transform: (Throwable) -> Error,
    block: () -> A
): A

Annotations

/**
 * DSL marker for Raise contexts
 */
@DslMarker
annotation class RaiseDSL

/**
 * Marks potentially unsafe Raise operations
 */
annotation class DelicateRaiseApi

/**
 * Marks experimental accumulation APIs
 */
annotation class ExperimentalRaiseAccumulateApi

/**
 * Marks experimental tracing APIs
 */
annotation class ExperimentalTraceApi

Install with Tessl CLI

npx tessl i tessl/maven-io-arrow-kt--arrow-core-iosarm64@2.1.2

docs

collection-extensions.md

core-data-types.md

effect-system.md

index.md

raise-dsl.md

tuple-types.md

tile.json