0
# Effect System
1
2
Suspended computations that can raise typed errors, providing a foundation for asynchronous and concurrent programming with proper error handling.
3
4
## Capabilities
5
6
### Effect<Error, A>
7
8
Suspended computation that can raise errors of type `Error` and produce values of type `A`.
9
10
```kotlin { .api }
11
/**
12
* Suspended computation that can raise typed errors
13
* @param Error Type of errors that can be raised
14
* @param A Type of successful result
15
*/
16
typealias Effect<Error, A> = suspend Raise<Error>.() -> A
17
18
/**
19
* Create a suspended Effect computation
20
* @param block Suspended computation in Raise context
21
* @return Either with error or successful result
22
*/
23
suspend fun <Error, A> effect(block: suspend Raise<Error>.() -> A): Either<Error, A>
24
25
/**
26
* Run Effect and handle both success and error cases
27
* @param onError Handler for error case
28
* @param onSuccess Handler for success case
29
* @return Result of appropriate handler
30
*/
31
suspend inline fun <Error, A, B> Effect<Error, A>.fold(
32
onError: (Error) -> B,
33
onSuccess: (A) -> B
34
): B
35
36
/**
37
* Transform error type of Effect
38
* @param transform Function to transform error
39
* @return Effect with transformed error type
40
*/
41
suspend inline fun <Error, NewError, A> Effect<Error, A>.mapError(
42
transform: (Error) -> NewError
43
): Either<NewError, A>
44
45
/**
46
* Transform success value of Effect
47
* @param transform Function to transform success value
48
* @return Effect with transformed success type
49
*/
50
suspend inline fun <Error, A, B> Effect<Error, A>.map(
51
transform: (A) -> B
52
): Either<Error, B>
53
54
/**
55
* Chain Effects together
56
* @param transform Function that produces next Effect
57
* @return Chained Effect
58
*/
59
suspend inline fun <Error, A, B> Effect<Error, A>.flatMap(
60
transform: suspend Raise<Error>.(A) -> B
61
): Either<Error, B>
62
63
/**
64
* Recover from errors in Effect
65
* @param fallback Handler for error case
66
* @return Effect result or fallback result
67
*/
68
suspend inline fun <Error, A> Effect<Error, A>.recover(
69
fallback: suspend Raise<Error>.(Error) -> A
70
): Either<Error, A>
71
72
/**
73
* Handle specific error types
74
* @param handler Handler for specific error
75
* @return Effect with handled errors
76
*/
77
suspend inline fun <Error, A> Effect<Error, A>.handleError(
78
handler: suspend Raise<Error>.(Error) -> A
79
): Either<Error, A>
80
81
/**
82
* Get result or provide fallback
83
* @param fallback Fallback value provider
84
* @return Success value or fallback
85
*/
86
suspend inline fun <Error, A> Effect<Error, A>.getOrElse(
87
fallback: suspend (Error) -> A
88
): A
89
```
90
91
**Usage Examples:**
92
93
```kotlin
94
import arrow.core.raise.*
95
import arrow.core.*
96
import kotlinx.coroutines.*
97
98
// Basic Effect computation
99
suspend fun fetchUser(id: String): Either<String, User> = effect {
100
val response = httpClient.get("/users/$id")
101
if (response.status != 200) raise("User not found: $id")
102
103
response.body<User>()
104
}
105
106
// Chaining Effects
107
suspend fun getUserProfile(id: String): Either<String, Profile> = effect {
108
val user = fetchUser(id).bind()
109
val profile = httpClient.get("/profiles/${user.id}")
110
if (profile.status != 200) raise("Profile not found for user: $id")
111
112
profile.body<Profile>()
113
}
114
115
// Error recovery
116
suspend fun getUserWithFallback(id: String): User =
117
fetchUser(id).getOrElse { User.anonymous() }
118
119
// Transforming results
120
suspend fun getUserName(id: String): Either<String, String> =
121
fetchUser(id).map { it.name }
122
123
// Handling multiple Effects
124
suspend fun loadDashboard(userId: String): Either<String, Dashboard> = effect {
125
val user = fetchUser(userId).bind()
126
val profile = getUserProfile(userId).bind()
127
val posts = async { fetchUserPosts(userId).bind() }
128
val friends = async { fetchUserFriends(userId).bind() }
129
130
Dashboard(user, profile, posts.await(), friends.await())
131
}
132
```
133
134
### EagerEffect<Error, A>
135
136
Non-suspended computation that can raise errors, useful for synchronous operations.
137
138
```kotlin { .api }
139
/**
140
* Non-suspended computation that can raise typed errors
141
* @param Error Type of errors that can be raised
142
* @param A Type of successful result
143
*/
144
typealias EagerEffect<Error, A> = Raise<Error>.() -> A
145
146
/**
147
* Create an eager Effect computation
148
* @param block Computation in Raise context
149
* @return Either with error or successful result
150
*/
151
fun <Error, A> eagerEffect(block: Raise<Error>.() -> A): Either<Error, A>
152
153
/**
154
* Fold over EagerEffect result
155
* @param onError Handler for error case
156
* @param onSuccess Handler for success case
157
* @return Result of appropriate handler
158
*/
159
inline fun <Error, A, B> EagerEffect<Error, A>.fold(
160
onError: (Error) -> B,
161
onSuccess: (A) -> B
162
): B
163
164
/**
165
* Transform error type of EagerEffect
166
* @param transform Function to transform error
167
* @return EagerEffect with transformed error type
168
*/
169
inline fun <Error, NewError, A> EagerEffect<Error, A>.mapError(
170
transform: (Error) -> NewError
171
): Either<NewError, A>
172
173
/**
174
* Transform success value of EagerEffect
175
* @param transform Function to transform success value
176
* @return EagerEffect with transformed success type
177
*/
178
inline fun <Error, A, B> EagerEffect<Error, A>.map(
179
transform: (A) -> B
180
): Either<Error, B>
181
182
/**
183
* Chain EagerEffects together
184
* @param transform Function that produces next EagerEffect
185
* @return Chained EagerEffect
186
*/
187
inline fun <Error, A, B> EagerEffect<Error, A>.flatMap(
188
transform: Raise<Error>.(A) -> B
189
): Either<Error, B>
190
191
/**
192
* Recover from errors in EagerEffect
193
* @param fallback Handler for error case
194
* @return EagerEffect result or fallback result
195
*/
196
inline fun <Error, A> EagerEffect<Error, A>.recover(
197
fallback: Raise<Error>.(Error) -> A
198
): Either<Error, A>
199
```
200
201
**Usage Examples:**
202
203
```kotlin
204
import arrow.core.raise.*
205
import arrow.core.*
206
207
// Basic EagerEffect computation
208
fun parseConfig(input: String): Either<String, Config> = eagerEffect {
209
val lines = input.lines()
210
if (lines.isEmpty()) raise("Config cannot be empty")
211
212
val pairs = lines.mapNotNull { line ->
213
val parts = line.split("=", limit = 2)
214
if (parts.size == 2) parts[0].trim() to parts[1].trim()
215
else null
216
}
217
218
if (pairs.isEmpty()) raise("No valid config entries found")
219
Config(pairs.toMap())
220
}
221
222
// Combining eager effects
223
fun validateAndParseConfig(input: String): Either<String, Config> = eagerEffect {
224
if (input.isBlank()) raise("Input cannot be blank")
225
226
val config = parseConfig(input).bind()
227
if (!config.hasRequiredFields()) raise("Missing required configuration fields")
228
229
config
230
}
231
232
// Error transformation
233
fun parseConfigWithDetails(input: String): Either<ConfigError, Config> =
234
parseConfig(input).mapError { ConfigError.ParseFailure(it) }
235
```
236
237
### Effect Utilities
238
239
Additional utilities for working with Effects.
240
241
```kotlin { .api }
242
/**
243
* Catch exceptions and convert to Either
244
* @param block Computation that might throw
245
* @return Either with caught exception or result
246
*/
247
suspend inline fun <A> catch(block: suspend () -> A): Either<Throwable, A>
248
249
/**
250
* Catch specific exception types
251
* @param block Computation that might throw
252
* @return Either with caught exception or result
253
*/
254
suspend inline fun <reified E : Throwable, A> catchSpecific(
255
block: suspend () -> A
256
): Either<E, A>
257
258
/**
259
* Convert nullable result to Option Effect
260
* @param block Computation that returns nullable
261
* @return Option with result
262
*/
263
suspend inline fun <A> ensureNotNull(block: suspend () -> A?): Option<A>
264
265
/**
266
* Parallel execution of Effects
267
* @param effects Collection of Effects to run in parallel
268
* @return List of results or first error encountered
269
*/
270
suspend fun <Error, A> Iterable<suspend () -> Either<Error, A>>.parTraverse(): Either<Error, List<A>>
271
272
/**
273
* Parallel execution with error accumulation
274
* @param combineError Function to combine errors
275
* @param effects Collection of Effects
276
* @return Results and/or accumulated errors
277
*/
278
suspend fun <Error, A> Iterable<suspend () -> Either<Error, A>>.parTraverseAccumulating(
279
combineError: (Error, Error) -> Error
280
): Either<Error, List<A>>
281
282
/**
283
* Race between Effects, returning first successful result
284
* @param effects Effects to race
285
* @return First successful result or combined errors
286
*/
287
suspend fun <Error, A> race(vararg effects: suspend () -> Either<Error, A>): Either<Error, A>
288
289
/**
290
* Timeout Effect computation
291
* @param timeoutMillis Timeout in milliseconds
292
* @param block Effect computation
293
* @return Result or timeout error
294
*/
295
suspend fun <Error, A> withTimeout(
296
timeoutMillis: Long,
297
block: suspend Raise<Error>.() -> A
298
): Either<Error, A>
299
```
300
301
**Usage Examples:**
302
303
```kotlin
304
import arrow.core.raise.*
305
import arrow.core.*
306
import kotlinx.coroutines.*
307
308
// Exception handling
309
suspend fun safeApiCall(url: String): Either<Throwable, ApiResponse> = catch {
310
httpClient.get(url).body<ApiResponse>()
311
}
312
313
// Parallel execution
314
suspend fun loadUserData(userId: String): Either<String, UserData> = effect {
315
val effects = listOf(
316
{ fetchUser(userId) },
317
{ fetchUserPosts(userId) },
318
{ fetchUserFriends(userId) }
319
)
320
321
val results = effects.parTraverse().bind()
322
UserData(results[0] as User, results[1] as List<Post>, results[2] as List<User>)
323
}
324
325
// Racing effects
326
suspend fun fetchFromMultipleSources(query: String): Either<String, SearchResult> = race(
327
{ searchPrimaryDb(query) },
328
{ searchSecondaryDb(query) },
329
{ searchCache(query) }
330
)
331
332
// Timeout handling
333
suspend fun fetchWithTimeout(url: String): Either<String, String> =
334
withTimeout(5000) {
335
httpClient.get(url).body<String>()
336
}
337
```
338
339
### Integration with Coroutines
340
341
Effects integrate seamlessly with Kotlin Coroutines for concurrent and asynchronous programming.
342
343
```kotlin { .api }
344
/**
345
* Convert Effect to Deferred
346
* @param scope Coroutine scope
347
* @param effect Effect to convert
348
* @return Deferred Either result
349
*/
350
fun <Error, A> CoroutineScope.async(
351
effect: suspend Raise<Error>.() -> A
352
): Deferred<Either<Error, A>>
353
354
/**
355
* Launch Effect in coroutine
356
* @param scope Coroutine scope
357
* @param effect Effect to launch
358
* @return Job for the launched coroutine
359
*/
360
fun <Error> CoroutineScope.launch(
361
effect: suspend Raise<Error>.() -> Unit,
362
onError: (Error) -> Unit = {}
363
): Job
364
365
/**
366
* Convert Flow to Effect
367
* @param flow Flow to collect
368
* @return Effect that collects the flow
369
*/
370
suspend fun <Error, A> Flow<Either<Error, A>>.collectEffect(): Either<Error, List<A>>
371
```
372
373
**Usage Examples:**
374
375
```kotlin
376
import arrow.core.raise.*
377
import arrow.core.*
378
import kotlinx.coroutines.*
379
import kotlinx.coroutines.flow.*
380
381
// Concurrent Effects
382
suspend fun loadUserDashboard(userId: String): Either<String, Dashboard> =
383
coroutineScope {
384
val userDeferred = async { fetchUser(userId) }
385
val postsDeferred = async { fetchUserPosts(userId) }
386
val friendsDeferred = async { fetchUserFriends(userId) }
387
388
effect {
389
val user = userDeferred.await().bind()
390
val posts = postsDeferred.await().bind()
391
val friends = friendsDeferred.await().bind()
392
393
Dashboard(user, posts, friends)
394
}
395
}
396
397
// Flow integration
398
suspend fun processUserStream(userIds: Flow<String>): Either<String, List<User>> =
399
userIds
400
.map { fetchUser(it) }
401
.collectEffect()
402
```