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

error-handling.mddocs/

0

# Error Handling

1

2

The Either type provides typed error handling as an alternative to exceptions. It represents computations that can either succeed with a value of type B or fail with an error of type A. Either is right-biased, meaning operations like map and flatMap work on the success (Right) case.

3

4

## Core Types

5

6

```kotlin { .api }

7

sealed class Either<out A, out B>

8

9

data class Left<out A>(val value: A) : Either<A, Nothing>()

10

11

data class Right<out B>(val value: B) : Either<Nothing, B>()

12

13

// Type alias for error accumulation

14

typealias EitherNel<E, A> = Either<NonEmptyList<E>, A>

15

```

16

17

## Construction

18

19

### Direct Construction

20

21

```kotlin { .api }

22

// Create Left (error) and Right (success)

23

fun <A> A.left(): Either<A, Nothing>

24

fun <A> A.right(): Either<Nothing, A>

25

```

26

27

### Safe Exception Handling

28

29

```kotlin { .api }

30

companion object Either {

31

// Catch all exceptions

32

fun <R> catch(f: () -> R): Either<Throwable, R>

33

34

// Catch specific exception types

35

fun <T : Throwable, R> catchOrThrow(f: () -> R): Either<T, R>

36

}

37

```

38

39

### Error Accumulation

40

41

```kotlin { .api }

42

companion object Either {

43

// Combine multiple Either values, accumulating errors

44

fun <E, A, B, Z> zipOrAccumulate(

45

combine: (E, E) -> E,

46

a: Either<E, A>,

47

b: Either<E, B>,

48

transform: (A, B) -> Z

49

): Either<E, Z>

50

51

// Non-empty list error accumulation (2-10 arity overloads)

52

fun <E, A, B, Z> zipOrAccumulate(

53

a: Either<E, A>,

54

b: Either<E, B>,

55

transform: (A, B) -> Z

56

): Either<NonEmptyList<E>, Z>

57

}

58

```

59

60

## Inspection

61

62

```kotlin { .api }

63

// Type checking

64

fun isLeft(): Boolean

65

fun isRight(): Boolean

66

fun isLeft(predicate: (A) -> Boolean): Boolean

67

fun isRight(predicate: (B) -> Boolean): Boolean

68

69

// Value extraction

70

fun getOrNull(): B?

71

fun leftOrNull(): A?

72

fun getOrElse(default: (A) -> B): B

73

```

74

75

## Transformation

76

77

```kotlin { .api }

78

// Transform Right values

79

fun <C> map(f: (B) -> C): Either<A, C>

80

fun <C> flatMap(f: (B) -> Either<A, C>): Either<A, C>

81

82

// Transform Left values

83

fun <C> mapLeft(f: (A) -> C): Either<C, B>

84

fun <C> handleErrorWith(f: (A) -> Either<C, B>): Either<C, B>

85

86

// Swap Left and Right

87

fun swap(): Either<B, A>

88

```

89

90

## Pattern Matching

91

92

```kotlin { .api }

93

// Fold operation

94

fun <C> fold(ifLeft: (A) -> C, ifRight: (B) -> C): C

95

```

96

97

## Side Effects

98

99

```kotlin { .api }

100

// Execute side effects

101

fun onLeft(action: (A) -> Unit): Either<A, B>

102

fun onRight(action: (B) -> Unit): Either<A, B>

103

```

104

105

## Conversion

106

107

```kotlin { .api }

108

// Convert to other types

109

fun toIor(): Ior<A, B>

110

fun getOrNone(): Option<B>

111

112

// Merge when both types are the same

113

fun Either<A, A>.merge(): A

114

115

// Flatten nested Either

116

fun Either<A, Either<A, B>>.flatten(): Either<A, B>

117

```

118

119

## Error Recovery

120

121

```kotlin { .api }

122

// Recover using Raise DSL

123

fun <EE> recover(recover: Raise<EE>.(A) -> B): Either<EE, B>

124

125

// Catch specific exceptions with Raise DSL

126

fun <E, T : Throwable> catch(catch: Raise<E>.(T) -> A): Either<E, A>

127

```

128

129

## Combining Either Values

130

131

```kotlin { .api }

132

// Combine two Either values

133

fun combine(

134

other: Either<A, B>,

135

combineLeft: (A, A) -> A,

136

combineRight: (B, B) -> B

137

): Either<A, B>

138

```

139

140

## Comparison

141

142

```kotlin { .api }

143

// Compare Either values (when components are Comparable)

144

operator fun <A : Comparable<A>, B : Comparable<B>> compareTo(other: Either<A, B>): Int

145

```

146

147

## NonEmptyList Error Handling

148

149

```kotlin { .api }

150

// Convert to NonEmptyList error form

151

fun <E, A> Either<E, A>.toEitherNel(): EitherNel<E, A>

152

153

// Create Left with NonEmptyList

154

fun <E> E.leftNel(): EitherNel<E, Nothing>

155

```

156

157

## Usage Examples

158

159

### Basic Error Handling

160

161

```kotlin

162

import arrow.core.*

163

164

// Safe division

165

fun divide(x: Int, y: Int): Either<String, Int> =

166

if (y == 0) "Division by zero".left()

167

else (x / y).right()

168

169

val success = divide(10, 2) // Right(5)

170

val error = divide(10, 0) // Left("Division by zero")

171

172

// Extract values

173

val result1 = success.getOrElse { 0 } // 5

174

val result2 = error.getOrElse { 0 } // 0

175

```

176

177

### Chaining Operations

178

179

```kotlin

180

import arrow.core.*

181

182

fun parseAge(input: String): Either<String, Int> =

183

input.toIntOrNull()?.right() ?: "Invalid number".left()

184

185

fun validateAge(age: Int): Either<String, Int> =

186

if (age >= 0) age.right() else "Negative age".left()

187

188

fun processAge(input: String): Either<String, String> =

189

parseAge(input)

190

.flatMap { validateAge(it) }

191

.map { "Age: $it years" }

192

193

val valid = processAge("25") // Right("Age: 25 years")

194

val invalid = processAge("-5") // Left("Negative age")

195

```

196

197

### Exception Handling

198

199

```kotlin

200

import arrow.core.*

201

202

// Catch exceptions safely

203

val result: Either<Throwable, String> = Either.catch {

204

"Hello World".substring(20) // Throws StringIndexOutOfBoundsException

205

}

206

207

when (result) {

208

is Either.Left -> println("Error: ${result.value.message}")

209

is Either.Right -> println("Success: ${result.value}")

210

}

211

```

212

213

### Error Accumulation

214

215

```kotlin

216

import arrow.core.*

217

218

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

219

220

fun validateName(name: String): Either<String, String> =

221

if (name.isNotBlank()) name.right() else "Name cannot be empty".left()

222

223

fun validateEmail(email: String): Either<String, String> =

224

if (email.contains('@')) email.right() else "Invalid email".left()

225

226

fun validateAge(age: Int): Either<String, Int> =

227

if (age >= 18) age.right() else "Must be 18 or older".left()

228

229

// Accumulate all errors

230

fun validateUser(name: String, email: String, age: Int): Either<NonEmptyList<String>, User> =

231

Either.zipOrAccumulate(

232

validateName(name),

233

validateEmail(email),

234

validateAge(age)

235

) { validName, validEmail, validAge ->

236

User(validName, validEmail, validAge)

237

}

238

239

val validUser = validateUser("Alice", "alice@example.com", 25)

240

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

241

242

val invalidUser = validateUser("", "invalid-email", 16)

243

// Left(NonEmptyList("Name cannot be empty", "Invalid email", "Must be 18 or older"))

244

```

245

246

### Integration with Raise DSL

247

248

```kotlin

249

import arrow.core.*

250

import arrow.core.raise.*

251

252

fun Raise<String>.processUser(name: String, age: Int): User {

253

ensure(name.isNotBlank()) { "Name cannot be empty" }

254

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

255

return User(name, "", age)

256

}

257

258

val result = either {

259

processUser("Alice", 25)

260

}

261

262

// Pattern matching

263

val message = result.fold(

264

ifLeft = { error -> "Error: $error" },

265

ifRight = { user -> "Created user: ${user.name}" }

266

)

267

```