or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

error-handling.mdindex.mdparallel-processing.mdracing.mdresource-management.mdsynchronization-flow.md

error-handling.mddocs/

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

```