or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collection-extensions.mderror-handling.mdindex.mdoptional-values.mdpartial-results.mdproduct-types.mdraise-dsl.mdsafe-collections.mdutility-functions.md

raise-dsl.mddocs/

0

# Raise DSL

1

2

The Raise DSL provides modern typed error handling with short-circuiting, error recovery, and accumulation patterns. It allows you to work with logical failures in a structured way, replacing exception-based error handling with typed errors.

3

4

## Core Interface

5

6

```kotlin { .api }

7

interface Raise<in Error> {

8

// Raise a logical failure and short-circuit

9

fun raise(r: Error): Nothing

10

11

// Bind operations for extracting success values

12

fun <A> Either<Error, A>.bind(): A

13

fun <A> Option<A>.bind(): A // raises None for None case

14

fun <A> EagerEffect<Error, A>.bind(): A

15

suspend fun <A> Effect<Error, A>.bind(): A

16

}

17

18

// DSL marker

19

@DslMarker

20

annotation class RaiseDSL

21

```

22

23

## DSL Builders

24

25

```kotlin { .api }

26

// Build Either using Raise DSL

27

fun <E, A> either(block: Raise<E>.() -> A): Either<E, A>

28

29

// Build Option using Raise DSL (raises None)

30

fun <A> option(block: Raise<None>.() -> A): Option<A>

31

32

// Error recovery

33

fun <E, A> recover(block: Raise<E>.() -> A, recover: (E) -> A): A

34

35

// Error transformation

36

fun <E, EE, A> recover(block: Raise<E>.() -> A, transform: Raise<EE>.(E) -> A): Either<EE, A>

37

```

38

39

## Validation Functions

40

41

```kotlin { .api }

42

// In Raise context

43

fun Raise<Error>.ensure(condition: Boolean, raise: () -> Error)

44

fun <A> Raise<Error>.ensureNotNull(value: A?, raise: () -> Error): A

45

```

46

47

## Error Accumulation

48

49

```kotlin { .api }

50

// Accumulate errors from multiple operations

51

fun <Error, A, B, Z> mapOrAccumulate(

52

a: () -> A,

53

b: () -> B,

54

transform: (A, B) -> Z

55

): Either<NonEmptyList<Error>, Z>

56

57

// With custom error combining

58

fun <Error, A, B, Z> mapOrAccumulate(

59

combine: (Error, Error) -> Error,

60

a: () -> A,

61

b: () -> B,

62

transform: (A, B) -> Z

63

): Either<Error, Z>

64

```

65

66

## Exception Handling

67

68

```kotlin { .api }

69

// Catch exceptions in Raise context

70

fun <E, A> catch(

71

raise: (Throwable) -> E,

72

block: () -> A

73

): Either<E, A>

74

75

// Catch specific exceptions

76

fun <E, T : Throwable, A> catch(

77

raise: (T) -> E,

78

block: () -> A

79

): Either<E, A>

80

```

81

82

## Usage Examples

83

84

### Basic Error Handling

85

86

```kotlin

87

import arrow.core.raise.*

88

import arrow.core.*

89

90

// Define error types

91

sealed class DivisionError {

92

object DivisionByZero : DivisionError()

93

data class InvalidInput(val input: String) : DivisionError()

94

}

95

96

// Function using Raise DSL

97

fun Raise<DivisionError>.safeDivide(x: String, y: String): Double {

98

val xNum = x.toDoubleOrNull()

99

?: raise(DivisionError.InvalidInput(x))

100

val yNum = y.toDoubleOrNull()

101

?: raise(DivisionError.InvalidInput(y))

102

103

ensure(yNum != 0.0) { DivisionError.DivisionByZero }

104

105

return xNum / yNum

106

}

107

108

// Use with either builder

109

val result1 = either { safeDivide("10", "2") } // Right(5.0)

110

val result2 = either { safeDivide("10", "0") } // Left(DivisionByZero)

111

val result3 = either { safeDivide("abc", "2") } // Left(InvalidInput("abc"))

112

```

113

114

### Error Recovery

115

116

```kotlin

117

import arrow.core.raise.*

118

import arrow.core.*

119

120

fun Raise<String>.parseAge(input: String): Int {

121

val age = input.toIntOrNull() ?: raise("Invalid number format")

122

ensure(age >= 0) { "Age cannot be negative" }

123

ensure(age <= 150) { "Age seems unrealistic" }

124

return age

125

}

126

127

// Recover with fallback value

128

val age1 = recover({ parseAge("25") }) { error ->

129

println("Error: $error")

130

0 // fallback

131

} // Result: 25

132

133

val age2 = recover({ parseAge("invalid") }) { error ->

134

println("Error: $error")

135

18 // default adult age

136

} // Result: 18

137

```

138

139

### Binding Operations

140

141

```kotlin

142

import arrow.core.raise.*

143

import arrow.core.*

144

145

data class User(val id: Int, val name: String, val email: String)

146

147

fun findUser(id: Int): Either<String, User> =

148

if (id > 0) User(id, "Alice", "alice@example.com").right()

149

else "Invalid user ID".left()

150

151

fun validateEmail(email: String): Option<String> =

152

if (email.contains('@')) email.some() else none()

153

154

// Combine operations using bind

155

fun Raise<String>.processUser(id: Int): String {

156

val user = findUser(id).bind() // Extracts User or raises String error

157

val validEmail = validateEmail(user.email).bind() // Raises None if invalid

158

return "User ${user.name} has valid email: $validEmail"

159

}

160

161

val result = either { processUser(1) }

162

// Right("User Alice has valid email: alice@example.com")

163

```

164

165

### Error Accumulation

166

167

```kotlin

168

import arrow.core.raise.*

169

import arrow.core.*

170

171

data class ValidationError(val field: String, val message: String)

172

data class UserForm(val name: String, val email: String, val age: Int)

173

174

fun Raise<ValidationError>.validateName(name: String): String {

175

ensure(name.isNotBlank()) { ValidationError("name", "Name is required") }

176

ensure(name.length >= 2) { ValidationError("name", "Name too short") }

177

return name

178

}

179

180

fun Raise<ValidationError>.validateEmail(email: String): String {

181

ensure(email.isNotBlank()) { ValidationError("email", "Email is required") }

182

ensure(email.contains('@')) { ValidationError("email", "Invalid email") }

183

return email

184

}

185

186

fun Raise<ValidationError>.validateAge(age: Int): Int {

187

ensure(age >= 18) { ValidationError("age", "Must be 18 or older") }

188

ensure(age <= 100) { ValidationError("age", "Age seems unrealistic") }

189

return age

190

}

191

192

// Accumulate all validation errors

193

fun validateUserForm(name: String, email: String, age: Int): Either<NonEmptyList<ValidationError>, UserForm> =

194

mapOrAccumulate(

195

{ validateName(name) },

196

{ validateEmail(email) },

197

{ validateAge(age) }

198

) { validName, validEmail, validAge ->

199

UserForm(validName, validEmail, validAge)

200

}

201

202

val valid = validateUserForm("Alice", "alice@example.com", 25)

203

// Right(UserForm("Alice", "alice@example.com", 25))

204

205

val invalid = validateUserForm("", "invalid-email", 16)

206

// Left(NonEmptyList(

207

// ValidationError("name", "Name is required"),

208

// ValidationError("email", "Invalid email"),

209

// ValidationError("age", "Must be 18 or older")

210

// ))

211

```

212

213

### Exception Integration

214

215

```kotlin

216

import arrow.core.raise.*

217

import arrow.core.*

218

219

sealed class FileError {

220

data class FileNotFound(val path: String) : FileError()

221

data class PermissionDenied(val path: String) : FileError()

222

data class IOError(val message: String) : FileError()

223

}

224

225

fun Raise<FileError>.readFile(path: String): String {

226

return catch(

227

raise = { throwable ->

228

when (throwable) {

229

is java.io.FileNotFoundException ->

230

FileError.FileNotFound(path)

231

is java.io.IOException ->

232

FileError.IOError(throwable.message ?: "Unknown IO error")

233

else -> FileError.IOError("Unexpected error: ${throwable.message}")

234

}

235

}

236

) {

237

// This could throw various exceptions

238

java.io.File(path).readText()

239

}.bind()

240

}

241

242

val content = either { readFile("config.txt") }

243

```

244

245

### Complex Workflows

246

247

```kotlin

248

import arrow.core.raise.*

249

import arrow.core.*

250

251

// Multi-step process with different error types

252

sealed class ProcessingError {

253

data class ParseError(val input: String) : ProcessingError()

254

data class ValidationError(val message: String) : ProcessingError()

255

data class TransformationError(val stage: String) : ProcessingError()

256

}

257

258

fun Raise<ProcessingError>.parseInput(input: String): Map<String, Any> {

259

ensure(input.isNotBlank()) { ProcessingError.ParseError("Empty input") }

260

// Simulate parsing JSON-like input

261

return mapOf("data" to input)

262

}

263

264

fun Raise<ProcessingError>.validateData(data: Map<String, Any>): Map<String, Any> {

265

ensure(data.containsKey("data")) {

266

ProcessingError.ValidationError("Missing data field")

267

}

268

return data

269

}

270

271

fun Raise<ProcessingError>.transformData(data: Map<String, Any>): String {

272

val value = data["data"] as? String

273

?: raise(ProcessingError.TransformationError("Data conversion"))

274

return value.uppercase()

275

}

276

277

// Chain operations in Raise context

278

fun Raise<ProcessingError>.processWorkflow(input: String): String {

279

val parsed = parseInput(input)

280

val validated = validateData(parsed)

281

return transformData(validated)

282

}

283

284

// Execute with error recovery

285

val workflow1 = recover({ processWorkflow("hello world") }) { error ->

286

when (error) {

287

is ProcessingError.ParseError -> "PARSE_ERROR"

288

is ProcessingError.ValidationError -> "VALIDATION_ERROR"

289

is ProcessingError.TransformationError -> "TRANSFORM_ERROR"

290

}

291

} // Result: "HELLO WORLD"

292

293

val workflow2 = recover({ processWorkflow("") }) { error ->

294

"ERROR: $error"

295

} // Result: "ERROR: ParseError(Empty input)"

296

```

297

298

### Integration with Option

299

300

```kotlin

301

import arrow.core.raise.*

302

import arrow.core.*

303

304

fun findUserById(id: Int): Option<String> =

305

if (id in 1..10) "user_$id".some() else none()

306

307

// Using Option with Raise DSL

308

fun Raise<String>.processUserChain(id: Int): String {

309

val user = findUserById(id).bind() // Raises "None" if Option is None

310

return "Processing: $user"

311

}

312

313

// Handle None as string error

314

val result1 = recover({ processUserChain(5) }) {

315

"User not found"

316

} // Result: "Processing: user_5"

317

318

val result2 = recover({ processUserChain(15) }) {

319

"User not found"

320

} // Result: "User not found"

321

```