0
# Error Handling
1
2
The Either type provides typed error handling as an alternative to exceptions. It represents computations that can either succeed with a value of type B or fail with an error of type A. Either is right-biased, meaning operations like map and flatMap work on the success (Right) case.
3
4
## Core Types
5
6
```kotlin { .api }
7
sealed class Either<out A, out B>
8
9
data class Left<out A>(val value: A) : Either<A, Nothing>()
10
11
data class Right<out B>(val value: B) : Either<Nothing, B>()
12
13
// Type alias for error accumulation
14
typealias EitherNel<E, A> = Either<NonEmptyList<E>, A>
15
```
16
17
## Construction
18
19
### Direct Construction
20
21
```kotlin { .api }
22
// Create Left (error) and Right (success)
23
fun <A> A.left(): Either<A, Nothing>
24
fun <A> A.right(): Either<Nothing, A>
25
```
26
27
### Safe Exception Handling
28
29
```kotlin { .api }
30
companion object Either {
31
// Catch all exceptions
32
fun <R> catch(f: () -> R): Either<Throwable, R>
33
34
// Catch specific exception types
35
fun <T : Throwable, R> catchOrThrow(f: () -> R): Either<T, R>
36
}
37
```
38
39
### Error Accumulation
40
41
```kotlin { .api }
42
companion object Either {
43
// Combine multiple Either values, accumulating errors
44
fun <E, A, B, Z> zipOrAccumulate(
45
combine: (E, E) -> E,
46
a: Either<E, A>,
47
b: Either<E, B>,
48
transform: (A, B) -> Z
49
): Either<E, Z>
50
51
// Non-empty list error accumulation (2-10 arity overloads)
52
fun <E, A, B, Z> zipOrAccumulate(
53
a: Either<E, A>,
54
b: Either<E, B>,
55
transform: (A, B) -> Z
56
): Either<NonEmptyList<E>, Z>
57
}
58
```
59
60
## Inspection
61
62
```kotlin { .api }
63
// Type checking
64
fun isLeft(): Boolean
65
fun isRight(): Boolean
66
fun isLeft(predicate: (A) -> Boolean): Boolean
67
fun isRight(predicate: (B) -> Boolean): Boolean
68
69
// Value extraction
70
fun getOrNull(): B?
71
fun leftOrNull(): A?
72
fun getOrElse(default: (A) -> B): B
73
```
74
75
## Transformation
76
77
```kotlin { .api }
78
// Transform Right values
79
fun <C> map(f: (B) -> C): Either<A, C>
80
fun <C> flatMap(f: (B) -> Either<A, C>): Either<A, C>
81
82
// Transform Left values
83
fun <C> mapLeft(f: (A) -> C): Either<C, B>
84
fun <C> handleErrorWith(f: (A) -> Either<C, B>): Either<C, B>
85
86
// Swap Left and Right
87
fun swap(): Either<B, A>
88
```
89
90
## Pattern Matching
91
92
```kotlin { .api }
93
// Fold operation
94
fun <C> fold(ifLeft: (A) -> C, ifRight: (B) -> C): C
95
```
96
97
## Side Effects
98
99
```kotlin { .api }
100
// Execute side effects
101
fun onLeft(action: (A) -> Unit): Either<A, B>
102
fun onRight(action: (B) -> Unit): Either<A, B>
103
```
104
105
## Conversion
106
107
```kotlin { .api }
108
// Convert to other types
109
fun toIor(): Ior<A, B>
110
fun getOrNone(): Option<B>
111
112
// Merge when both types are the same
113
fun Either<A, A>.merge(): A
114
115
// Flatten nested Either
116
fun Either<A, Either<A, B>>.flatten(): Either<A, B>
117
```
118
119
## Error Recovery
120
121
```kotlin { .api }
122
// Recover using Raise DSL
123
fun <EE> recover(recover: Raise<EE>.(A) -> B): Either<EE, B>
124
125
// Catch specific exceptions with Raise DSL
126
fun <E, T : Throwable> catch(catch: Raise<E>.(T) -> A): Either<E, A>
127
```
128
129
## Combining Either Values
130
131
```kotlin { .api }
132
// Combine two Either values
133
fun combine(
134
other: Either<A, B>,
135
combineLeft: (A, A) -> A,
136
combineRight: (B, B) -> B
137
): Either<A, B>
138
```
139
140
## Comparison
141
142
```kotlin { .api }
143
// Compare Either values (when components are Comparable)
144
operator fun <A : Comparable<A>, B : Comparable<B>> compareTo(other: Either<A, B>): Int
145
```
146
147
## NonEmptyList Error Handling
148
149
```kotlin { .api }
150
// Convert to NonEmptyList error form
151
fun <E, A> Either<E, A>.toEitherNel(): EitherNel<E, A>
152
153
// Create Left with NonEmptyList
154
fun <E> E.leftNel(): EitherNel<E, Nothing>
155
```
156
157
## Usage Examples
158
159
### Basic Error Handling
160
161
```kotlin
162
import arrow.core.*
163
164
// Safe division
165
fun divide(x: Int, y: Int): Either<String, Int> =
166
if (y == 0) "Division by zero".left()
167
else (x / y).right()
168
169
val success = divide(10, 2) // Right(5)
170
val error = divide(10, 0) // Left("Division by zero")
171
172
// Extract values
173
val result1 = success.getOrElse { 0 } // 5
174
val result2 = error.getOrElse { 0 } // 0
175
```
176
177
### Chaining Operations
178
179
```kotlin
180
import arrow.core.*
181
182
fun parseAge(input: String): Either<String, Int> =
183
input.toIntOrNull()?.right() ?: "Invalid number".left()
184
185
fun validateAge(age: Int): Either<String, Int> =
186
if (age >= 0) age.right() else "Negative age".left()
187
188
fun processAge(input: String): Either<String, String> =
189
parseAge(input)
190
.flatMap { validateAge(it) }
191
.map { "Age: $it years" }
192
193
val valid = processAge("25") // Right("Age: 25 years")
194
val invalid = processAge("-5") // Left("Negative age")
195
```
196
197
### Exception Handling
198
199
```kotlin
200
import arrow.core.*
201
202
// Catch exceptions safely
203
val result: Either<Throwable, String> = Either.catch {
204
"Hello World".substring(20) // Throws StringIndexOutOfBoundsException
205
}
206
207
when (result) {
208
is Either.Left -> println("Error: ${result.value.message}")
209
is Either.Right -> println("Success: ${result.value}")
210
}
211
```
212
213
### Error Accumulation
214
215
```kotlin
216
import arrow.core.*
217
218
data class User(val name: String, val email: String, val age: Int)
219
220
fun validateName(name: String): Either<String, String> =
221
if (name.isNotBlank()) name.right() else "Name cannot be empty".left()
222
223
fun validateEmail(email: String): Either<String, String> =
224
if (email.contains('@')) email.right() else "Invalid email".left()
225
226
fun validateAge(age: Int): Either<String, Int> =
227
if (age >= 18) age.right() else "Must be 18 or older".left()
228
229
// Accumulate all errors
230
fun validateUser(name: String, email: String, age: Int): Either<NonEmptyList<String>, User> =
231
Either.zipOrAccumulate(
232
validateName(name),
233
validateEmail(email),
234
validateAge(age)
235
) { validName, validEmail, validAge ->
236
User(validName, validEmail, validAge)
237
}
238
239
val validUser = validateUser("Alice", "alice@example.com", 25)
240
// Right(User("Alice", "alice@example.com", 25))
241
242
val invalidUser = validateUser("", "invalid-email", 16)
243
// Left(NonEmptyList("Name cannot be empty", "Invalid email", "Must be 18 or older"))
244
```
245
246
### Integration with Raise DSL
247
248
```kotlin
249
import arrow.core.*
250
import arrow.core.raise.*
251
252
fun Raise<String>.processUser(name: String, age: Int): User {
253
ensure(name.isNotBlank()) { "Name cannot be empty" }
254
ensure(age >= 18) { "Must be 18 or older" }
255
return User(name, "", age)
256
}
257
258
val result = either {
259
processUser("Alice", 25)
260
}
261
262
// Pattern matching
263
val message = result.fold(
264
ifLeft = { error -> "Error: $error" },
265
ifRight = { user -> "Created user: ${user.name}" }
266
)
267
```