0
# Raise DSL
1
2
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.
3
4
## Capabilities
5
6
### Raise<Error>
7
8
Core interface for typed error handling that allows raising errors and binding over various error-containing types.
9
10
```kotlin { .api }
11
/**
12
* Core interface for typed error handling
13
* @param Error The type of errors that can be raised
14
*/
15
@RaiseDSL
16
interface Raise<in Error> {
17
/**
18
* Raise an error, short-circuiting the computation
19
* @param error The error to raise
20
* @return Nothing (never returns)
21
*/
22
fun raise(error: Error): Nothing
23
24
/**
25
* Extract the success value from Either, or raise the error
26
* @param receiver Either to bind over
27
* @return Success value if Right, raises error if Left
28
*/
29
fun <A> Either<Error, A>.bind(): A
30
31
/**
32
* Extract value from Option, raising Unit if None (requires SingletonRaise)
33
* @param receiver Option to bind over
34
* @return Value if Some, raises if None
35
*/
36
fun <A> Option<A>.bind(): A // Available in SingletonRaise context
37
38
/**
39
* Extract success value from Ior, accumulating any left values
40
* @param receiver Ior to bind over
41
* @return Right or Both right value, accumulates left values
42
*/
43
fun <A> Ior<Error, A>.bind(): A // Available in IorRaise context
44
45
/**
46
* Bind over all Either values in a collection
47
* @param receiver Collection of Either values
48
* @return Collection of success values, raises first error encountered
49
*/
50
fun <A> Iterable<Either<Error, A>>.bindAll(): List<A>
51
fun <A> NonEmptyList<Either<Error, A>>.bindAll(): NonEmptyList<A>
52
fun <A> NonEmptySet<Either<Error, A>>.bindAll(): NonEmptySet<A>
53
fun <K, A> Map<K, Either<Error, A>>.bindAll(): Map<K, A>
54
}
55
```
56
57
**Usage Examples:**
58
59
```kotlin
60
import arrow.core.raise.*
61
import arrow.core.*
62
63
// Basic error raising
64
fun Raise<String>.parsePositiveInt(input: String): Int {
65
val number = input.toIntOrNull() ?: raise("Not a valid integer: $input")
66
if (number <= 0) raise("Number must be positive: $number")
67
return number
68
}
69
70
// Using Either DSL
71
val result = either {
72
val x = parsePositiveInt("42") // 42
73
val y = parsePositiveInt("0") // Raises "Number must be positive: 0"
74
x + y // This line never executes
75
} // Left("Number must be positive: 0")
76
77
// Binding over Either values
78
val computation = either {
79
val a = Either.right(10).bind() // 10
80
val b = Either.right(20).bind() // 20
81
val c = Either.left("error").bind() // Raises "error"
82
a + b + c // Never executes
83
} // Left("error")
84
```
85
86
### SingletonRaise<Error>
87
88
Specialized Raise implementation for handling nullable values and Option types.
89
90
```kotlin { .api }
91
/**
92
* Specialized Raise for unit-based errors, typically for null handling
93
* @param Error Error type (often Unit for simple null checks)
94
*/
95
class SingletonRaise<in Error>(private val raise: Raise<Unit>) : Raise<Error> {
96
/**
97
* Ensure a condition is true, raising unit error if false
98
* @param condition Boolean condition to check
99
*/
100
fun ensure(condition: Boolean): Unit
101
102
/**
103
* Ensure a value is not null, raising unit error if null
104
* @param value Nullable value to check
105
* @return Non-null value
106
*/
107
fun <A> ensureNotNull(value: A?): A
108
109
/**
110
* Bind over nullable values
111
* @param receiver Nullable value
112
* @return Non-null value or raises
113
*/
114
fun <A> A?.bind(): A
115
116
/**
117
* Bind over Option values
118
* @param receiver Option to bind over
119
* @return Value if Some, raises if None
120
*/
121
fun <A> Option<A>.bind(): A
122
123
/**
124
* Bind over collections of nullable values
125
*/
126
fun <A> Iterable<A?>.bindAll(): List<A>
127
fun <A> NonEmptyList<A?>.bindAll(): NonEmptyList<A>
128
fun <K, A> Map<K, A?>.bindAll(): Map<K, A>
129
130
/**
131
* Recover from errors with fallback computation
132
*/
133
inline fun <A> recover(
134
block: SingletonRaise<Error>.() -> A,
135
fallback: () -> A
136
): A
137
}
138
```
139
140
**Usage Examples:**
141
142
```kotlin
143
import arrow.core.raise.*
144
import arrow.core.*
145
146
// Working with nullable values
147
val result = option {
148
val name = "Alice".takeIf { it.isNotEmpty() }.bind() // "Alice"
149
val age = "25".toIntOrNull().bind() // 25
150
val email = null.bind() // Raises, short-circuits
151
"$name ($age) - $email" // Never executes
152
} // None
153
154
// Validation with ensure
155
val validation = option {
156
val input = "42"
157
ensure(input.isNotBlank()) { } // Passes
158
val number = input.toIntOrNull().bind()
159
ensureNotNull(number.takeIf { it > 0 }) // 42
160
} // Some(42)
161
162
// Binding collections
163
val numbers = option {
164
listOf("1", "2", null, "4").mapNotNull { it?.toIntOrNull() }.bind() // Fails on null
165
} // None
166
```
167
168
### IorRaise<Error>
169
170
Raise implementation that accumulates errors while preserving successful computations.
171
172
```kotlin { .api }
173
/**
174
* Raise implementation for error accumulation with Ior
175
* @param Error Error type that can be combined
176
*/
177
class IorRaise<Error>(
178
val combineError: (Error, Error) -> Error,
179
private val combineRef: Atomic<Any?>,
180
private val raise: Raise<Error>
181
) : Raise<Error> {
182
/**
183
* Accumulate an error without short-circuiting
184
* @param error Error to accumulate
185
*/
186
fun accumulate(error: Error): Unit
187
188
/**
189
* Combine an error with any previously accumulated errors
190
* @param error Error to combine
191
* @return Combined error
192
*/
193
fun combine(error: Error): Error
194
195
/**
196
* Bind over Ior values, accumulating left values
197
* @param receiver Ior to bind over
198
* @return Right value, accumulates any left values
199
*/
200
fun <A> Ior<Error, A>.bind(): A
201
202
/**
203
* Get value from Either or accumulate the error and provide fallback
204
* @param receiver Either to process
205
* @param fallback Function to provide fallback value
206
* @return Right value or result of fallback after accumulating error
207
*/
208
fun <A> Either<Error, A>.getOrAccumulate(fallback: (Error) -> A): A
209
210
/**
211
* Recover from accumulated errors
212
*/
213
inline fun <A> recover(
214
block: IorRaise<Error>.() -> A,
215
fallback: (Error) -> A
216
): A
217
}
218
```
219
220
**Usage Examples:**
221
222
```kotlin
223
import arrow.core.raise.*
224
import arrow.core.*
225
226
// Error accumulation with IorRaise
227
data class ValidationError(val message: String)
228
229
fun combineErrors(e1: ValidationError, e2: ValidationError): ValidationError =
230
ValidationError("${e1.message}; ${e2.message}")
231
232
val result = ior(::combineErrors) {
233
val name = validateName("").getOrAccumulate { ValidationError("Name is required") }
234
val email = validateEmail("invalid").getOrAccumulate { ValidationError("Invalid email") }
235
val age = validateAge(-1).getOrAccumulate { ValidationError("Age must be positive") }
236
237
User(name, email, age) // Creates user even with accumulated errors
238
} // Ior.Both(ValidationError("Name is required; Invalid email; Age must be positive"), User(...))
239
```
240
241
### DSL Builder Functions
242
243
Convenient functions for creating computations in the Raise context.
244
245
```kotlin { .api }
246
/**
247
* Create an Either computation with error handling
248
* @param block Computation that can raise errors of type Error
249
* @return Either.Left with error or Either.Right with result
250
*/
251
inline fun <Error, A> either(block: Raise<Error>.() -> A): Either<Error, A>
252
253
/**
254
* Create an Option computation with null handling
255
* @param block Computation that can fail (raise Unit)
256
* @return Some with result or None if computation failed
257
*/
258
inline fun <A> option(block: SingletonRaise<Unit>.() -> A): Option<A>
259
260
/**
261
* Create an Ior computation with error accumulation
262
* @param combineError Function to combine multiple errors
263
* @param block Computation that can raise and accumulate errors
264
* @return Ior with accumulated errors and/or result
265
*/
266
inline fun <Error, A> ior(
267
combineError: (Error, Error) -> Error,
268
block: IorRaise<Error>.() -> A
269
): Ior<Error, A>
270
271
/**
272
* Create a nullable computation
273
* @param block Computation that can fail
274
* @return Result value or null if computation failed
275
*/
276
inline fun <A> nullable(block: SingletonRaise<Unit>.() -> A): A?
277
278
/**
279
* Recover from errors in Either computation
280
* @param block Computation that might fail
281
* @param fallback Function to handle errors
282
* @return Result of computation or fallback
283
*/
284
inline fun <Error, A> recover(
285
block: Raise<Error>.() -> A,
286
fallback: (Error) -> A
287
): A
288
289
/**
290
* Fold over Either result with error and success handlers
291
* @param block Computation that might fail
292
* @param onError Handler for error case
293
* @param onSuccess Handler for success case
294
* @return Result of appropriate handler
295
*/
296
inline fun <Error, A, B> fold(
297
block: Raise<Error>.() -> A,
298
onError: (Error) -> B,
299
onSuccess: (A) -> B
300
): B
301
```
302
303
**Usage Examples:**
304
305
```kotlin
306
import arrow.core.raise.*
307
import arrow.core.*
308
309
// Either computation
310
val parseResult = either {
311
val input = "not-a-number"
312
input.toIntOrNull() ?: raise("Invalid number: $input")
313
} // Left("Invalid number: not-a-number")
314
315
// Option computation with nullable handling
316
val safeComputation = option {
317
val user = findUser("123").bind() // Returns null if not found
318
val profile = user.profile.bind() // Returns null if no profile
319
profile.displayName.bind() // Returns null if no display name
320
} // None if any step returns null
321
322
// Recovery with fallback
323
val withFallback = recover(
324
{ parsePositiveInt("-5") } // Raises error
325
) { error -> 0 } // 0
326
327
// Folding over computation
328
val message = fold(
329
{ parsePositiveInt("42") },
330
onError = { "Error: $it" },
331
onSuccess = { "Success: $it" }
332
) // "Success: 42"
333
```
334
335
### Effect System
336
337
Suspended computations that can raise typed errors, providing a foundation for asynchronous and concurrent programming with proper error handling.
338
339
```kotlin { .api }
340
/**
341
* Suspended computation that can raise typed errors
342
* @param Error The type of errors that can be raised
343
* @param A The type of successful result
344
*/
345
typealias Effect<Error, A> = suspend Raise<Error>.() -> A
346
347
/**
348
* Non-suspended computation that can raise typed errors
349
* @param Error The type of errors that can be raised
350
* @param A The type of successful result
351
*/
352
typealias EagerEffect<Error, A> = Raise<Error>.() -> A
353
354
/**
355
* Execute a suspended Effect computation
356
* @param block The Effect computation to execute
357
* @return Either containing the error or successful result
358
*/
359
suspend fun <Error, A> effect(block: suspend Raise<Error>.() -> A): Either<Error, A>
360
361
/**
362
* Execute an eager Effect computation
363
* @param block The EagerEffect computation to execute
364
* @return Either containing the error or successful result
365
*/
366
fun <Error, A> eagerEffect(block: Raise<Error>.() -> A): Either<Error, A>
367
368
/**
369
* Execute an Effect and fold over the result
370
* @param block The Effect computation to execute
371
* @param onError Function to handle error case
372
* @param onSuccess Function to handle success case
373
*/
374
suspend fun <Error, A, B> Effect<Error, A>.fold(
375
onError: (Error) -> B,
376
onSuccess: (A) -> B
377
): B
378
379
/**
380
* Map over the success value of an Effect
381
* @param transform Function to transform the success value
382
*/
383
suspend fun <Error, A, B> Effect<Error, A>.map(
384
transform: suspend (A) -> B
385
): Effect<Error, B>
386
387
/**
388
* Bind two Effects together
389
* @param transform Function that takes success value and returns new Effect
390
*/
391
suspend fun <Error, A, B> Effect<Error, A>.flatMap(
392
transform: suspend (A) -> Effect<Error, B>
393
): Effect<Error, B>
394
395
/**
396
* Handle errors in an Effect computation
397
* @param recover Function to recover from errors
398
*/
399
suspend fun <Error, A, E2> Effect<Error, A>.handleErrorWith(
400
recover: suspend (Error) -> Effect<E2, A>
401
): Effect<E2, A>
402
403
/**
404
* Get the result of an Effect, returning null if it fails
405
* @return The success value or null if error was raised
406
*/
407
suspend fun <Error, A> Effect<Error, A>.getOrNull(): A?
408
409
/**
410
* Merge Effect where Error and A are the same type
411
* @return The value, whether it was error or success
412
*/
413
suspend fun <A> Effect<A, A>.merge(): A
414
415
/**
416
* Execute multiple Effects in parallel, accumulating results
417
* @param effects Collection of Effects to execute
418
* @return Effect containing list of all results
419
*/
420
suspend fun <Error, A> Collection<Effect<Error, A>>.parZip(): Effect<Error, List<A>>
421
```
422
423
**Usage Examples:**
424
425
```kotlin
426
import arrow.core.raise.*
427
import arrow.core.*
428
import kotlinx.coroutines.*
429
430
// Suspended Effect computation
431
suspend fun fetchUserProfile(userId: String): Effect<String, UserProfile> = effect {
432
val user = fetchUser(userId) ?: raise("User not found: $userId")
433
val profile = fetchProfile(user.id) ?: raise("Profile not found for user: $userId")
434
435
UserProfile(user.name, profile.bio, profile.avatar)
436
}
437
438
// Using Effect with error handling
439
suspend fun handleUserRequest(userId: String) {
440
fetchUserProfile(userId).fold(
441
onError = { error ->
442
println("Failed to fetch profile: $error")
443
respondWithError(error)
444
},
445
onSuccess = { profile ->
446
println("Successfully fetched profile for ${profile.name}")
447
respondWithProfile(profile)
448
}
449
)
450
}
451
452
// Chaining Effects
453
suspend fun processUserData(userId: String): Effect<String, ProcessedData> = effect {
454
val profile = fetchUserProfile(userId).bind()
455
val settings = fetchUserSettings(userId).bind()
456
val preferences = fetchUserPreferences(userId).bind()
457
458
ProcessedData(profile, settings, preferences)
459
}
460
461
// Parallel execution
462
suspend fun fetchAllData(userIds: List<String>): Effect<String, List<UserProfile>> = effect {
463
userIds.map { userId ->
464
fetchUserProfile(userId)
465
}.parZip().bind()
466
}
467
468
// Error recovery
469
suspend fun fetchWithFallback(userId: String): Effect<String, UserProfile> = effect {
470
fetchUserProfile(userId)
471
.handleErrorWith { error ->
472
if (error.contains("not found")) {
473
effect { getDefaultProfile() }
474
} else {
475
effect { raise(error) }
476
}
477
}
478
.bind()
479
}
480
```
481
482
### RaiseAccumulate System
483
484
Advanced error accumulation capabilities for collecting multiple errors while continuing computation.
485
486
```kotlin { .api }
487
/**
488
* Raise context for accumulating errors with NonEmptyList
489
* @param Error The type of errors to accumulate
490
*/
491
class RaiseAccumulate<Error> : Raise<NonEmptyList<Error>> {
492
/**
493
* Map over a collection, accumulating errors
494
* @param transform Function that can raise errors
495
* @return Collection of results or accumulated errors
496
*/
497
fun <A, B> Iterable<A>.mapOrAccumulate(
498
transform: Raise<Error>.(A) -> B
499
): List<B>
500
501
/**
502
* Zip multiple values, accumulating errors
503
* @param action Function combining all values
504
* @return Combined result or accumulated errors
505
*/
506
fun <A, B, C> zipOrAccumulate(
507
first: Raise<Error>.() -> A,
508
second: Raise<Error>.() -> B,
509
action: (A, B) -> C
510
): C
511
512
// Additional zipOrAccumulate overloads for 3-10 parameters...
513
}
514
515
/**
516
* Execute computation with error accumulation
517
* @param block Computation that can accumulate errors
518
* @return Either with accumulated errors or success
519
*/
520
fun <Error, A> mapOrAccumulate(
521
block: RaiseAccumulate<Error>.() -> A
522
): Either<NonEmptyList<Error>, A>
523
```
524
525
**Usage Examples:**
526
527
```kotlin
528
import arrow.core.raise.*
529
import arrow.core.*
530
531
data class ValidationError(val field: String, val message: String)
532
533
fun validateUser(userData: UserData): Either<NonEmptyList<ValidationError>, ValidUser> =
534
mapOrAccumulate {
535
zipOrAccumulate(
536
{ validateName(userData.name) },
537
{ validateEmail(userData.email) },
538
{ validateAge(userData.age) }
539
) { name, email, age ->
540
ValidUser(name, email, age)
541
}
542
}
543
544
fun validateName(name: String): Raise<ValidationError>.() -> String = {
545
if (name.isBlank()) raise(ValidationError("name", "Name cannot be blank"))
546
if (name.length < 2) raise(ValidationError("name", "Name too short"))
547
name
548
}
549
550
// Processing multiple items with accumulation
551
fun validateAllUsers(users: List<UserData>): Either<NonEmptyList<ValidationError>, List<ValidUser>> =
552
mapOrAccumulate {
553
users.mapOrAccumulate { userData ->
554
validateUser(userData).bind()
555
}
556
}
557
```
558
559
### Utility Functions
560
561
Additional utility functions for advanced error handling patterns.
562
563
```kotlin { .api }
564
/**
565
* Ensure a condition is true, otherwise raise an error
566
* @param condition Boolean condition to check
567
* @param raise Function to produce error if condition is false
568
*/
569
fun <Error> Raise<Error>.ensure(condition: Boolean, raise: () -> Error): Unit
570
571
/**
572
* Ensure a value is not null, otherwise raise an error
573
* @param value Nullable value to check
574
* @param raise Function to produce error if value is null
575
* @return Non-null value
576
*/
577
fun <Error, A : Any> Raise<Error>.ensureNotNull(value: A?, raise: () -> Error): A
578
579
/**
580
* Transform errors before raising them
581
* @param transform Function to transform errors
582
* @param block Computation that can raise transformed errors
583
*/
584
inline fun <Error, A, TransformedError> withError(
585
transform: (Error) -> TransformedError,
586
block: Raise<Error>.() -> A
587
): Raise<TransformedError>.() -> A
588
589
/**
590
* Catch exceptions and convert to raised errors
591
* @param transform Function to convert exception to error
592
* @param block Computation that might throw exceptions
593
* @return Result or raised error
594
*/
595
inline fun <Error, A> Raise<Error>.catch(
596
transform: (Throwable) -> Error,
597
block: () -> A
598
): A
599
```
600
601
### Annotations
602
603
```kotlin { .api }
604
/**
605
* DSL marker for Raise contexts
606
*/
607
@DslMarker
608
annotation class RaiseDSL
609
610
/**
611
* Marks potentially unsafe Raise operations
612
*/
613
annotation class DelicateRaiseApi
614
615
/**
616
* Marks experimental accumulation APIs
617
*/
618
annotation class ExperimentalRaiseAccumulateApi
619
620
/**
621
* Marks experimental tracing APIs
622
*/
623
annotation class ExperimentalTraceApi
624
```