0
# Error Handling
1
2
Arrow FX Coroutines integrates seamlessly with Arrow's Raise system to provide structured error handling capabilities. This enables functional error management patterns including error accumulation, typed error handling, and composable error strategies.
3
4
## Bracket Operations with Error Handling
5
6
### ExitCase for Error Information
7
8
```kotlin { .api }
9
sealed class ExitCase {
10
object Completed : ExitCase()
11
data class Cancelled(val exception: CancellationException) : ExitCase()
12
data class Failure(val failure: Throwable) : ExitCase()
13
14
companion object {
15
fun ExitCase(error: Throwable): ExitCase
16
}
17
}
18
```
19
20
ExitCase provides detailed information about how an operation terminated, enabling precise error handling and cleanup strategies.
21
22
### Cancellation Handling
23
24
```kotlin { .api }
25
suspend fun <A> onCancel(fa: suspend () -> A, onCancel: suspend () -> Unit): A
26
```
27
28
Register a handler that executes only when the operation is cancelled.
29
30
```kotlin
31
val result = onCancel(
32
fa = { performLongRunningTask() },
33
onCancel = {
34
println("Task was cancelled, cleaning up...")
35
cleanupResources()
36
}
37
)
38
```
39
40
### Guaranteed Execution
41
42
```kotlin { .api }
43
suspend fun <A> guarantee(fa: suspend () -> A, finalizer: suspend () -> Unit): A
44
suspend fun <A> guaranteeCase(fa: suspend () -> A, finalizer: suspend (ExitCase) -> Unit): A
45
```
46
47
Guarantee that cleanup code runs regardless of how the operation exits.
48
49
```kotlin
50
val result = guaranteeCase(
51
fa = { processData() },
52
finalizer = { exitCase ->
53
when (exitCase) {
54
is ExitCase.Completed -> logSuccess()
55
is ExitCase.Cancelled -> logCancellation()
56
is ExitCase.Failure -> logError(exitCase.failure)
57
}
58
}
59
)
60
```
61
62
### Bracket Pattern with Error Handling
63
64
```kotlin { .api }
65
suspend fun <A, B> bracket(
66
acquire: suspend () -> A,
67
use: suspend (A) -> B,
68
release: suspend (A) -> Unit
69
): B
70
71
suspend fun <A, B> bracketCase(
72
acquire: suspend () -> A,
73
use: suspend (A) -> B,
74
release: suspend (A, ExitCase) -> Unit
75
): B
76
```
77
78
Safe resource management with detailed exit case handling.
79
80
```kotlin
81
val result = bracketCase(
82
acquire = { openDatabaseConnection() },
83
use = { connection ->
84
connection.executeQuery("SELECT * FROM users")
85
},
86
release = { connection, exitCase ->
87
when (exitCase) {
88
is ExitCase.Completed -> {
89
connection.commit()
90
connection.close()
91
}
92
is ExitCase.Cancelled -> {
93
connection.rollback()
94
connection.close()
95
}
96
is ExitCase.Failure -> {
97
connection.rollback()
98
connection.close()
99
logError("Database operation failed", exitCase.failure)
100
}
101
}
102
}
103
)
104
```
105
106
## Error Accumulation in Parallel Operations
107
108
### Parallel Map with Error Accumulation
109
110
```kotlin { .api }
111
suspend fun <A, B> Iterable<A>.parMapOrAccumulate(
112
f: suspend CoroutineScope.(A) -> B
113
): Either<NonEmptyList<Error>, List<B>>
114
115
suspend fun <A, B> Iterable<A>.parMapOrAccumulate(
116
context: CoroutineContext,
117
f: suspend CoroutineScope.(A) -> B
118
): Either<NonEmptyList<Error>, List<B>>
119
120
suspend fun <A, B> Iterable<A>.parMapOrAccumulate(
121
concurrency: Int,
122
f: suspend CoroutineScope.(A) -> B
123
): Either<NonEmptyList<Error>, List<B>>
124
125
suspend fun <A, B> Iterable<A>.parMapOrAccumulate(
126
context: CoroutineContext,
127
concurrency: Int,
128
f: suspend CoroutineScope.(A) -> B
129
): Either<NonEmptyList<Error>, List<B>>
130
```
131
132
Execute parallel mapping operations while accumulating all errors instead of failing fast.
133
134
```kotlin
135
data class ValidationError(val field: String, val message: String)
136
137
val validationResults = users.parMapOrAccumulate { user ->
138
validateUser(user) // Function that can raise ValidationError
139
}
140
141
when (validationResults) {
142
is Either.Left -> {
143
println("Validation failed with errors:")
144
validationResults.value.forEach { error ->
145
println("- ${error.field}: ${error.message}")
146
}
147
}
148
is Either.Right -> {
149
println("All users validated successfully")
150
processValidUsers(validationResults.value)
151
}
152
}
153
```
154
155
### Parallel Zip with Error Accumulation
156
157
```kotlin { .api }
158
suspend fun <A, B, C> Raise<E>.parZipOrAccumulate(
159
fa: suspend CoroutineScope.() -> A,
160
fb: suspend CoroutineScope.() -> B,
161
f: suspend CoroutineScope.(A, B) -> C
162
): C
163
164
suspend fun <A, B, C> Raise<NonEmptyList<E>>.parZipOrAccumulate(
165
fa: suspend CoroutineScope.() -> A,
166
fb: suspend CoroutineScope.() -> B,
167
f: suspend CoroutineScope.(A, B) -> C
168
): C
169
```
170
171
Execute parallel operations within a Raise context, accumulating errors from all operations.
172
173
```kotlin
174
val userProfile = either<NonEmptyList<ValidationError>, UserProfile> {
175
parZipOrAccumulate(
176
{ validateName(userData.name) },
177
{ validateEmail(userData.email) },
178
{ validateAge(userData.age) },
179
{ validateAddress(userData.address) }
180
) { name, email, age, address ->
181
UserProfile(name, email, age, address)
182
}
183
}
184
```
185
186
## Raise Integration Types
187
188
### ScopedRaiseAccumulate
189
190
```kotlin { .api }
191
interface ScopedRaiseAccumulate<Error> : CoroutineScope, RaiseAccumulate<Error>
192
```
193
194
Intersection type that combines `CoroutineScope` and `RaiseAccumulate` for advanced error handling scenarios.
195
196
### RaiseScope for Racing
197
198
```kotlin { .api }
199
interface RaiseScope<in Error> : CoroutineScope, Raise<Error>
200
typealias RaiseHandler<Error> = suspend CoroutineScope.(Error) -> Nothing
201
```
202
203
Specialized scope type for racing operations with error handling.
204
205
## Advanced Error Handling Patterns
206
207
### Error Recovery Strategies
208
209
```kotlin
210
suspend fun <T> withRetry(
211
maxAttempts: Int,
212
operation: suspend () -> T
213
): Either<Exception, T> = either {
214
var lastException: Exception? = null
215
216
repeat(maxAttempts) { attempt ->
217
try {
218
return@either operation()
219
} catch (e: Exception) {
220
lastException = e
221
if (attempt < maxAttempts - 1) {
222
delay(1000 * (attempt + 1)) // Exponential backoff
223
}
224
}
225
}
226
227
raise(lastException!!)
228
}
229
```
230
231
### Fallback Chains
232
233
```kotlin
234
suspend fun <T> withFallbackChain(
235
operations: List<suspend () -> T>
236
): Either<NonEmptyList<Exception>, T> = either {
237
val errors = mutableListOf<Exception>()
238
239
for (operation in operations) {
240
try {
241
return@either operation()
242
} catch (e: Exception) {
243
errors.add(e)
244
}
245
}
246
247
raise(NonEmptyList.fromListUnsafe(errors))
248
}
249
```
250
251
### Validation Pipelines
252
253
```kotlin
254
data class User(val name: String, val email: String, val age: Int)
255
data class ValidationError(val field: String, val message: String)
256
257
suspend fun validateUser(userData: UserData): Either<NonEmptyList<ValidationError>, User> =
258
either {
259
parZipOrAccumulate(
260
{ validateName(userData.name) },
261
{ validateEmail(userData.email) },
262
{ validateAge(userData.age) }
263
) { name, email, age ->
264
User(name, email, age)
265
}
266
}
267
268
suspend fun validateName(name: String): String =
269
if (name.isBlank()) raise(ValidationError("name", "Name cannot be blank"))
270
else name.trim()
271
272
suspend fun validateEmail(email: String): String =
273
if (!email.contains("@")) raise(ValidationError("email", "Invalid email format"))
274
else email.lowercase()
275
276
suspend fun validateAge(age: Int): Int =
277
if (age < 0) raise(ValidationError("age", "Age must be positive"))
278
else age
279
```
280
281
## Error Handling with Resources
282
283
### Resource Error Propagation
284
285
```kotlin
286
val result = either<DatabaseError, ProcessingResult> {
287
resourceScope {
288
val connection = databaseResource
289
.releaseCase { conn, exitCase ->
290
when (exitCase) {
291
is ExitCase.Failure -> {
292
conn.rollback()
293
logError("Database transaction failed", exitCase.failure)
294
}
295
else -> conn.commit()
296
}
297
conn.close()
298
}
299
.bind()
300
301
// Use connection - errors automatically handled
302
processDataWithConnection(connection)
303
}
304
}
305
```
306
307
### Nested Resource Error Handling
308
309
```kotlin
310
suspend fun processWithMultipleResources(): Either<ProcessingError, Result> = either {
311
resourceScope {
312
val database = databaseResource.bind()
313
val cache = cacheResource.bind()
314
val httpClient = httpClientResource.bind()
315
316
val data = parZipOrAccumulate(
317
{ database.fetchUsers() },
318
{ cache.getConfiguration() },
319
{ httpClient.fetchMetadata() }
320
) { users, config, metadata ->
321
ProcessingInput(users, config, metadata)
322
}.bind()
323
324
processData(data)
325
}
326
}
327
```
328
329
## Integration Examples
330
331
### Error Handling with Racing
332
333
```kotlin
334
suspend fun fetchDataWithFallback(): Either<ServiceError, String> = either {
335
racing<String> {
336
raceOrThrow(raise = { error -> raise(error) }) {
337
primaryService.getData() // Can raise ServiceError
338
}
339
340
raceOrThrow(raise = { error -> raise(error) }) {
341
delay(2000)
342
fallbackService.getData() // Can raise ServiceError
343
}
344
}
345
}
346
```
347
348
### Complex Error Accumulation
349
350
```kotlin
351
data class ProcessingError(val stage: String, val error: String)
352
353
suspend fun processDataPipeline(
354
inputs: List<InputData>
355
): Either<NonEmptyList<ProcessingError>, List<OutputData>> = either {
356
val validated = inputs.parMapOrAccumulate { input ->
357
validateInput(input).mapLeft { error ->
358
ProcessingError("validation", error.message)
359
}.bind()
360
}.bind()
361
362
val processed = validated.parMapOrAccumulate { validInput ->
363
processInput(validInput).mapLeft { error ->
364
ProcessingError("processing", error.message)
365
}.bind()
366
}.bind()
367
368
val enriched = processed.parMapOrAccumulate { processedInput ->
369
enrichData(processedInput).mapLeft { error ->
370
ProcessingError("enrichment", error.message)
371
}.bind()
372
}.bind()
373
374
enriched
375
}
376
```