or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collection-extensions.mdcore-data-types.mdeffect-system.mdindex.mdraise-dsl.mdtuple-types.md

effect-system.mddocs/

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

```