or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collections.mderror-handling.mdfunctional-utilities.mdinclusive-or.mdindex.mdoptional-values.mdraise-dsl.md

raise-dsl.mddocs/

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

email

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

```