0
# Error Handling with Either
1
2
Type-safe error handling that makes failure cases explicit and composable. Either is right-biased, treating success (Right) as the default path while providing extensive combinators for error handling and composition.
3
4
## Capabilities
5
6
### Either Construction
7
8
Create Either instances representing success or failure states.
9
10
```kotlin { .api }
11
/**
12
* Create a successful Either containing the given value
13
*/
14
fun <B> Either.Companion.Right(value: B): Either<Nothing, B>
15
16
/**
17
* Create a failed Either containing the given error
18
*/
19
fun <A> Either.Companion.Left(value: A): Either<A, Nothing>
20
21
/**
22
* Execute a function and catch any thrown exceptions
23
*/
24
fun <R> Either.Companion.catch(f: () -> R): Either<Throwable, R>
25
26
/**
27
* Execute a function and catch specific exception types
28
*/
29
inline fun <reified T : Throwable, R> Either.Companion.catchOrThrow(
30
f: () -> R
31
): Either<T, R>
32
```
33
34
**Usage Examples:**
35
36
```kotlin
37
// Direct construction
38
val success: Either<String, Int> = Either.Right(42)
39
val failure: Either<String, Int> = Either.Left("Error occurred")
40
41
// Exception handling
42
val result = Either.catch { "123".toInt() } // Either<Throwable, Int>
43
val parsed = Either.catchOrThrow<NumberFormatException, Int> {
44
"abc".toInt()
45
} // Either<NumberFormatException, Int>
46
```
47
48
### Either Extension Functions
49
50
Convenience functions for creating Either instances from values.
51
52
```kotlin { .api }
53
/**
54
* Wrap any value in Either.Right
55
*/
56
fun <A> A.right(): Either<Nothing, A>
57
58
/**
59
* Wrap any value in Either.Left
60
*/
61
fun <A> A.left(): Either<A, Nothing>
62
63
/**
64
* Create an EitherNel with a single error in NonEmptyList
65
*/
66
fun <E> E.leftNel(): EitherNel<E, Nothing>
67
```
68
69
### Either Type Guards
70
71
Check the state of Either instances with type-safe guards.
72
73
```kotlin { .api }
74
/**
75
* Check if Either is Left (error case)
76
* @return true if Left, false if Right
77
*/
78
fun <A, B> Either<A, B>.isLeft(): Boolean
79
80
/**
81
* Check if Either is Right (success case)
82
* @return true if Right, false if Left
83
*/
84
fun <A, B> Either<A, B>.isRight(): Boolean
85
86
/**
87
* Check if Either is Left and matches predicate
88
*/
89
fun <A, B> Either<A, B>.isLeft(predicate: (A) -> Boolean): Boolean
90
91
/**
92
* Check if Either is Right and matches predicate
93
*/
94
fun <A, B> Either<A, B>.isRight(predicate: (B) -> Boolean): Boolean
95
```
96
97
### Either Pattern Matching
98
99
Safely extract values or perform operations based on Either state.
100
101
```kotlin { .api }
102
/**
103
* Pattern match on Either, providing handlers for both cases
104
*/
105
fun <A, B, C> Either<A, B>.fold(
106
ifLeft: (A) -> C,
107
ifRight: (B) -> C
108
): C
109
110
/**
111
* Extract the Right value or compute a default from Left
112
*/
113
fun <A, B> Either<A, B>.getOrElse(default: (A) -> B): B
114
115
/**
116
* Extract the Right value or return null
117
*/
118
fun <A, B> Either<A, B>.getOrNull(): B?
119
120
/**
121
* Extract the Left value or return null
122
*/
123
fun <A, B> Either<A, B>.leftOrNull(): A?
124
125
/**
126
* Convert Either to Option, keeping only Right values
127
*/
128
fun <A, B> Either<A, B>.getOrNone(): Option<B>
129
```
130
131
### Either Transformations
132
133
Transform Either values while preserving the container structure.
134
135
```kotlin { .api }
136
/**
137
* Transform the Right value if present
138
*/
139
fun <A, B, C> Either<A, B>.map(f: (B) -> C): Either<A, C>
140
141
/**
142
* Transform the Left value if present
143
*/
144
fun <A, B, C> Either<A, B>.mapLeft(f: (A) -> C): Either<C, B>
145
146
/**
147
* Monadic bind - chain operations that can fail
148
*/
149
fun <A, B, C> Either<A, B>.flatMap(f: (B) -> Either<A, C>): Either<A, C>
150
151
/**
152
* Swap Left and Right positions
153
*/
154
fun <A, B> Either<A, B>.swap(): Either<B, A>
155
156
/**
157
* Flatten nested Either (for Either<A, Either<A, B>>)
158
*/
159
fun <A, B> Either<A, Either<A, B>>.flatten(): Either<A, B>
160
161
/**
162
* Merge Either<A, A> into A
163
*/
164
fun <A> Either<A, A>.merge(): A
165
```
166
167
### Either Side Effects
168
169
Perform side effects based on Either state without changing the value.
170
171
```kotlin { .api }
172
/**
173
* Execute action if Either is Right, return original Either
174
*/
175
fun <A, B> Either<A, B>.onRight(action: (B) -> Unit): Either<A, B>
176
177
/**
178
* Execute action if Either is Left, return original Either
179
*/
180
fun <A, B> Either<A, B>.onLeft(action: (A) -> Unit): Either<A, B>
181
```
182
183
### Either Recovery
184
185
Recover from errors or handle specific exception types.
186
187
```kotlin { .api }
188
/**
189
* Recover from Left values using Raise DSL
190
*/
191
fun <A, B, EE> Either<A, B>.recover(
192
recover: Raise<EE>.(A) -> B
193
): Either<EE, B>
194
195
/**
196
* Catch and handle specific exception types
197
*/
198
inline fun <E, reified T : Throwable, A> Either<E, A>.catch(
199
catch: Raise<E>.(T) -> A
200
): Either<E, A>
201
```
202
203
### Either Accumulation
204
205
Combine multiple Either values, accumulating errors instead of short-circuiting.
206
207
```kotlin { .api }
208
/**
209
* Combine 2 Either values, accumulating errors with custom combiner
210
*/
211
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
212
combine: (E, E) -> E,
213
a: Either<E, A>,
214
b: Either<E, B>,
215
transform: (A, B) -> Z
216
): Either<E, Z>
217
218
/**
219
* Combine 2 Either values, accumulating errors in NonEmptyList
220
*/
221
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
222
a: Either<E, A>,
223
b: Either<E, B>,
224
transform: (A, B) -> Z
225
): Either<NonEmptyList<E>, Z>
226
227
// Similar functions exist for 3-10 parameters
228
```
229
230
**Usage Example:**
231
232
```kotlin
233
data class User(val name: String, val email: String, val age: Int)
234
235
fun validateName(name: String): Either<String, String> =
236
if (name.isNotBlank()) name.right() else "Name cannot be blank".left()
237
238
fun validateEmail(email: String): Either<String, String> =
239
if (email.contains("@")) email.right() else "Invalid email".left()
240
241
fun validateAge(age: Int): Either<String, Int> =
242
if (age >= 0) age.right() else "Age cannot be negative".left()
243
244
// Accumulate all validation errors
245
val userResult = Either.zipOrAccumulate(
246
validateName(""),
247
validateEmail("invalid-email"),
248
validateAge(-5)
249
) { name, email, age -> User(name, email, age) }
250
// Result: Either.Left(NonEmptyList("Name cannot be blank", "Invalid email", "Age cannot be negative"))
251
```
252
253
### Either Conversions
254
255
Convert Either to other Arrow types.
256
257
```kotlin { .api }
258
/**
259
* Convert Either to EitherNel
260
*/
261
fun <A, B> Either<A, B>.toEitherNel(): EitherNel<A, B>
262
263
/**
264
* Convert Either to Ior
265
*/
266
fun <A, B> Either<A, B>.toIor(): Ior<A, B>
267
```
268
269
### zipOrAccumulate - Error Accumulation
270
271
Combine multiple Either values, accumulating errors instead of short-circuiting on the first failure.
272
273
```kotlin { .api }
274
/**
275
* Combine 2 Either values with custom error combination
276
*/
277
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
278
combine: (E, E) -> E,
279
a: Either<E, A>,
280
b: Either<E, B>,
281
transform: (A, B) -> Z
282
): Either<E, Z>
283
284
/**
285
* Combine 2 Either values accumulating errors in NonEmptyList
286
*/
287
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
288
a: Either<E, A>,
289
b: Either<E, B>,
290
transform: (A, B) -> Z
291
): Either<NonEmptyList<E>, Z>
292
293
/**
294
* Combine 3 Either values with custom error combination
295
*/
296
fun <E, A, B, C, Z> Either.Companion.zipOrAccumulate(
297
combine: (E, E) -> E,
298
a: Either<E, A>,
299
b: Either<E, B>,
300
c: Either<E, C>,
301
transform: (A, B, C) -> Z
302
): Either<E, Z>
303
304
/**
305
* Combine 3 Either values accumulating errors in NonEmptyList
306
*/
307
fun <E, A, B, C, Z> Either.Companion.zipOrAccumulate(
308
a: Either<E, A>,
309
b: Either<E, B>,
310
c: Either<E, C>,
311
transform: (A, B, C) -> Z
312
): Either<NonEmptyList<E>, Z>
313
314
// Additional overloads available for 4-10 parameters with both custom combine and NonEmptyList accumulation
315
316
/**
317
* Combine 2 EitherNel values accumulating errors
318
*/
319
fun <E, A, B, Z> Either.Companion.zipOrAccumulate(
320
a: EitherNel<E, A>,
321
b: EitherNel<E, B>,
322
transform: (A, B) -> Z
323
): EitherNel<E, Z>
324
325
// Additional EitherNel overloads available for 3-10 parameters
326
```
327
328
**Usage Examples:**
329
330
```kotlin
331
// Custom error combination
332
val result1 = Either.zipOrAccumulate(
333
{ e1, e2 -> "$e1; $e2" },
334
parseAge("invalid"),
335
parseName(""),
336
::User
337
) // Either.Left("Invalid age; Name required")
338
339
// Error accumulation with NonEmptyList
340
val result2 = Either.zipOrAccumulate(
341
validateEmail("invalid-email"),
342
validateAge("-5"),
343
validateName("")
344
) { email, age, name -> User(email, age, name) }
345
// Either.Left(NonEmptyList("Invalid email", "Age must be positive", "Name required"))
346
```
347
348
## Types
349
350
### EitherNel Type Alias
351
352
```kotlin { .api }
353
/**
354
* Either with NonEmptyList for error accumulation
355
*/
356
typealias EitherNel<E, A> = Either<NonEmptyList<E>, A>
357
```