0
# Raise DSL for Typed Error Handling
1
2
Modern DSL for typed error handling using structured concurrency patterns. Raise provides ergonomic builders that eliminate boilerplate while maintaining type safety, serving as the recommended approach for error handling in Arrow Core.
3
4
## Capabilities
5
6
### Core Raise Interface
7
8
The foundation of the Raise DSL providing basic error-raising operations.
9
10
```kotlin { .api }
11
/**
12
* Core interface for raising typed errors
13
*/
14
interface Raise<in Error> {
15
/**
16
* Short-circuit the computation with the given error
17
*/
18
@RaiseDSL
19
fun raise(r: Error): Nothing
20
21
/**
22
* Ensure a condition is true, or raise an error
23
*/
24
@RaiseDSL
25
fun ensure(condition: Boolean, raise: () -> Error): Unit
26
27
/**
28
* Ensure a value is not null, or raise an error
29
*/
30
@RaiseDSL
31
fun <A> ensureNotNull(value: A?, raise: () -> Error): A
32
}
33
```
34
35
### DSL Builders
36
37
Entry points for creating Raise DSL computations that return different container types.
38
39
```kotlin { .api }
40
/**
41
* Create an Either computation using Raise DSL
42
*/
43
fun <Error, A> either(block: Raise<Error>.() -> A): Either<Error, A>
44
45
/**
46
* Create an Option computation using Raise DSL
47
*/
48
fun <A> option(block: OptionRaise.() -> A): Option<A>
49
50
/**
51
* Create a nullable computation using Raise DSL
52
*/
53
fun <A> nullable(block: NullableRaise.() -> A): A?
54
55
/**
56
* Create a Result computation using Raise DSL
57
*/
58
fun <A> result(block: ResultRaise.() -> A): Result<A>
59
60
/**
61
* Create an Ior computation with error combination
62
*/
63
fun <Error, A> ior(
64
combineError: (Error, Error) -> Error,
65
block: IorRaise<Error>.() -> A
66
): Ior<Error, A>
67
68
/**
69
* Create an IorNel computation with error accumulation
70
*/
71
fun <Error, A> iorNel(
72
block: IorRaise<NonEmptyList<Error>>.() -> A
73
): IorNel<Error, A>
74
```
75
76
**Usage Examples:**
77
78
```kotlin
79
// Either computation
80
val result = either<String, Int> {
81
val x = ensure(condition) { "Condition failed" }
82
val y = ensureNotNull(getValue()) { "Value was null" }
83
x + y
84
}
85
86
// Option computation
87
val maybeResult = option<String> {
88
val value = bindOrRaise(findValue())
89
value.uppercase()
90
}
91
92
// Result computation with exception handling
93
val safeResult = result<Int> {
94
val input = ensureNotNull(getInput()) { IllegalArgumentException("No input") }
95
input.toInt()
96
}
97
```
98
99
### Bind Operations
100
101
Extract values from container types or raise their error/empty cases.
102
103
```kotlin { .api }
104
/**
105
* Extract Right value from Either or raise Left
106
*/
107
context(Raise<Error>)
108
fun <Error, A> Either<Error, A>.bind(): A
109
110
/**
111
* Extract Some value from Option or raise None
112
*/
113
context(OptionRaise)
114
fun <A> Option<A>.bind(): A
115
116
/**
117
* Extract non-null value or raise null
118
*/
119
context(NullableRaise)
120
fun <A> A?.bind(): A
121
122
/**
123
* Extract success value from Result or raise failure
124
*/
125
context(ResultRaise)
126
fun <A> Result<A>.bind(): A
127
128
/**
129
* Extract Right value from Ior or raise Left
130
*/
131
context(IorRaise<Error>)
132
fun <Error, A> Ior<Error, A>.bind(): A
133
```
134
135
**Usage Examples:**
136
137
```kotlin
138
fun parseAndAdd(a: String, b: String): Either<String, Int> = either {
139
val numA = parseNumber(a).bind() // Extract or raise parse error
140
val numB = parseNumber(b).bind()
141
numA + numB
142
}
143
144
fun processUser(id: String): Option<String> = option {
145
val user = findUser(id).bind() // Extract user or raise None
146
val profile = getProfile(user.id).bind()
147
"${user.name} - ${profile.title}"
148
}
149
150
fun parseNumber(s: String): Either<String, Int> = either {
151
Either.catch { s.toInt() }
152
.mapLeft { "Invalid number: $s" }
153
.bind()
154
}
155
```
156
157
### Specialized Raise Types
158
159
Specific Raise implementations for different error types.
160
161
```kotlin { .api }
162
/**
163
* Raise interface specialized for Option (raises None)
164
*/
165
interface OptionRaise : Raise<None> {
166
override fun raise(r: None): Nothing
167
}
168
169
/**
170
* Raise interface specialized for nullable types (raises null)
171
*/
172
interface NullableRaise : Raise<Null> {
173
override fun raise(r: Null): Nothing
174
}
175
176
/**
177
* Raise interface specialized for Result (raises Throwable)
178
*/
179
interface ResultRaise : Raise<Throwable> {
180
override fun raise(r: Throwable): Nothing
181
}
182
183
/**
184
* Raise interface for Ior computations
185
*/
186
interface IorRaise<in Error> : Raise<Error> {
187
/**
188
* Add a warning/info value alongside continuing computation
189
*/
190
fun <A> info(info: Error, value: A): A
191
}
192
```
193
194
### Error Accumulation
195
196
Accumulate multiple errors instead of short-circuiting on the first error.
197
198
```kotlin { .api }
199
/**
200
* Interface for accumulating errors during computation
201
*/
202
interface RaiseAccumulate<in Error> : Raise<Error> {
203
/**
204
* Transform each element, accumulating any errors
205
*/
206
fun <A, B> Iterable<A>.mapOrAccumulate(
207
transform: RaiseAccumulate<Error>.(A) -> B
208
): List<B>
209
}
210
211
/**
212
* Transform iterable elements, accumulating errors in NonEmptyList
213
*/
214
fun <Error, A, B> mapOrAccumulate(
215
iterable: Iterable<A>,
216
transform: RaiseAccumulate<Error>.(A) -> B
217
): Either<NonEmptyList<Error>, List<B>>
218
219
/**
220
* Combine multiple Either values, accumulating errors
221
*/
222
fun <Error, A, B, C> zipOrAccumulate(
223
fa: Either<Error, A>,
224
fb: Either<Error, B>,
225
f: (A, B) -> C
226
): Either<NonEmptyList<Error>, C>
227
228
// Similar functions exist for 3-10 parameters
229
```
230
231
**Usage Examples:**
232
233
```kotlin
234
data class ValidationError(val field: String, val message: String)
235
236
fun validateUser(user: UserInput): Either<NonEmptyList<ValidationError>, User> {
237
return zipOrAccumulate(
238
validateName(user.name),
239
validateEmail(user.email),
240
validateAge(user.age)
241
) { name, email, age ->
242
User(name, email, age)
243
}
244
}
245
246
fun validateEmails(emails: List<String>): Either<NonEmptyList<String>, List<String>> {
247
return mapOrAccumulate(emails) { email ->
248
ensure(email.contains("@")) { "Invalid email: $email" }
249
250
}
251
}
252
```
253
254
### Contextual Binding
255
256
Alternative syntax for bind operations using context receivers.
257
258
```kotlin { .api }
259
/**
260
* Context receiver syntax for Either binding
261
*/
262
context(Raise<Error>)
263
fun <Error, A> Either<Error, A>.bind(): A
264
265
/**
266
* Context receiver syntax for Option binding
267
*/
268
context(Raise<None>)
269
fun <A> Option<A>.bind(): A
270
271
/**
272
* Context receiver syntax for nullable binding
273
*/
274
context(Raise<Null>)
275
fun <A> A?.bind(): A
276
```
277
278
### Recovery and Catch
279
280
Handle and recover from specific error types within Raise computations.
281
282
```kotlin { .api }
283
/**
284
* Recover from specific error types within a Raise computation
285
*/
286
context(Raise<NewError>)
287
fun <OldError, NewError, A> recover(
288
block: Raise<OldError>.() -> A,
289
recover: (OldError) -> A
290
): A
291
292
/**
293
* Catch and handle exceptions within a Raise computation
294
*/
295
context(Raise<Error>)
296
fun <Error, A> catch(
297
block: () -> A,
298
catch: (Throwable) -> Error
299
): A
300
```
301
302
**Usage Examples:**
303
304
```kotlin
305
fun processWithFallback(input: String): Either<String, Int> = either {
306
recover({
307
// This might raise a parsing error
308
parseComplexNumber(input).bind()
309
}) { parseError ->
310
// Fallback to simple parsing
311
input.toIntOrNull() ?: raise("Cannot parse: $input")
312
}
313
}
314
315
fun safeComputation(): Either<String, String> = either {
316
catch({
317
riskyOperation()
318
}) { exception ->
319
raise("Operation failed: ${exception.message}")
320
}
321
}
322
```
323
324
## Error Handling Patterns
325
326
### Validation Pattern
327
328
Accumulate validation errors across multiple fields.
329
330
```kotlin
331
fun validateUserRegistration(
332
name: String,
333
email: String,
334
age: Int,
335
password: String
336
): Either<NonEmptyList<ValidationError>, User> {
337
return zipOrAccumulate(
338
validateName(name),
339
validateEmail(email),
340
validateAge(age),
341
validatePassword(password)
342
) { validName, validEmail, validAge, validPassword ->
343
User(validName, validEmail, validAge, validPassword)
344
}
345
}
346
```
347
348
### Chain Pattern
349
350
Chain operations that can fail, short-circuiting on first error.
351
352
```kotlin
353
fun processUserData(userId: String): Either<ServiceError, ProcessedData> = either {
354
val user = userService.findUser(userId).bind()
355
val profile = profileService.getProfile(user.id).bind()
356
val permissions = authService.getPermissions(user.id).bind()
357
358
ensure(permissions.canAccessData) {
359
ServiceError.Unauthorized("User lacks data access")
360
}
361
362
ProcessedData(user, profile, permissions)
363
}
364
```
365
366
### Resource Management Pattern
367
368
Handle resource cleanup with proper error propagation.
369
370
```kotlin
371
fun processFile(filename: String): Either<FileError, String> = either {
372
val file = openFile(filename).bind()
373
val content = try {
374
processFileContent(file).bind()
375
} finally {
376
file.close()
377
}
378
content
379
}
380
```