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

partial-results.mddocs/

0

# Partial Results

1

2

The Ior (Inclusive OR) type represents computations that can have both success values and accumulated context/warnings simultaneously. Unlike Either which is exclusive (either error OR success), Ior can represent Left (only error), Right (only success), or Both (error AND success).

3

4

## Core Types

5

6

```kotlin { .api }

7

sealed class Ior<out A, out B>

8

9

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

10

11

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

12

13

data class Both<out A, out B>(val leftValue: A, val rightValue: B) : Ior<A, B>()

14

15

// Type alias for NonEmptyList context

16

typealias IorNel<A, B> = Ior<Nel<A>, B>

17

```

18

19

## Construction

20

21

### Direct Construction

22

23

```kotlin { .api }

24

// Create Left, Right, or Both

25

fun <A> A.leftIor(): Ior<A, Nothing>

26

fun <A> A.rightIor(): Ior<Nothing, A>

27

fun <A, B> Pair<A, B>.bothIor(): Ior<A, B>

28

```

29

30

### Static Constructors

31

32

```kotlin { .api }

33

companion object Ior {

34

// From nullable values - returns null if both are null

35

fun <A, B> fromNullables(a: A?, b: B?): Ior<A, B>?

36

37

// NonEmptyList variants

38

fun <A, B> leftNel(a: A): IorNel<A, B>

39

fun <A, B> bothNel(a: A, b: B): IorNel<A, B>

40

}

41

```

42

43

## Inspection

44

45

```kotlin { .api }

46

// Type checking

47

fun isLeft(): Boolean

48

fun isRight(): Boolean

49

fun isBoth(): Boolean

50

51

// Predicate-based checking

52

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

53

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

54

fun isBoth(leftPredicate: (A) -> Boolean, rightPredicate: (B) -> Boolean): Boolean

55

56

// Value extraction

57

fun getOrNull(): B? // Returns right value or null

58

fun leftOrNull(): A? // Returns left value or null

59

```

60

61

## Transformation

62

63

```kotlin { .api }

64

// Transform right values (preserves left in Both case)

65

fun <D> map(f: (B) -> D): Ior<A, D>

66

67

// Transform left values (preserves right in Both case)

68

fun <C> mapLeft(fa: (A) -> C): Ior<C, B>

69

70

// Monadic bind - requires combine function for left values

71

fun <D> flatMap(combine: (A, A) -> A, f: (B) -> Ior<A, D>): Ior<A, D>

72

73

// Error handling - requires combine function for right values

74

fun <D> handleErrorWith(combine: (B, B) -> B, f: (A) -> Ior<D, B>): Ior<D, B>

75

76

// Swap left and right types

77

fun swap(): Ior<B, A>

78

```

79

80

## Pattern Matching

81

82

```kotlin { .api }

83

// Three-way fold

84

fun <C> fold(fa: (A) -> C, fb: (B) -> C, fab: (A, B) -> C): C

85

```

86

87

## Conversion

88

89

```kotlin { .api }

90

// Convert to Either (ignores left value in Both case)

91

fun toEither(): Either<A, B>

92

93

// Convert to Pair with nullables

94

fun toPair(): Pair<A?, B?>

95

96

// Isomorphic conversion

97

fun unwrap(): Either<Either<A, B>, Pair<A, B>>

98

99

// Extract right value with default for Left

100

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

101

```

102

103

## Combining Ior Values

104

105

```kotlin { .api }

106

// Combine two Ior values

107

fun combine(

108

other: Ior<A, B>,

109

combineA: (A, A) -> A,

110

combineB: (B, B) -> B

111

): Ior<A, B>

112

113

// Flatten nested Ior

114

fun Ior<A, Ior<A, B>>.flatten(combine: (A, A) -> A): Ior<A, B>

115

```

116

117

## Comparison

118

119

```kotlin { .api }

120

// Compare Ior values (when components are Comparable)

121

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

122

```

123

124

## NonEmptyList Context

125

126

```kotlin { .api }

127

// Convert to NonEmptyList context form

128

fun <A, B> Ior<A, B>.toIorNel(): IorNel<A, B>

129

```

130

131

## Usage Examples

132

133

### Basic Operations

134

135

```kotlin

136

import arrow.core.*

137

138

// Create different Ior variants

139

val leftOnly = "warning".leftIor<String, Int>() // Left("warning")

140

val rightOnly = 42.rightIor<String, Int>() // Right(42)

141

val both = Ior.Both("warning", 42) // Both("warning", 42)

142

143

// Transform values

144

val doubled = rightOnly.map { it * 2 } // Right(84)

145

val bothDoubled = both.map { it * 2 } // Both("warning", 84)

146

147

// Extract values

148

val result1 = rightOnly.getOrElse { 0 } // 42

149

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

150

val result3 = both.getOrElse { 0 } // 42 (has right value)

151

```

152

153

### Accumulating Context

154

155

```kotlin

156

import arrow.core.*

157

158

// Parsing with warnings

159

fun parseNumber(input: String): Ior<List<String>, Int> {

160

val warnings = mutableListOf<String>()

161

162

if (input.startsWith("0") && input.length > 1) {

163

warnings.add("Leading zero detected")

164

}

165

166

return input.toIntOrNull()?.let { number ->

167

if (warnings.isEmpty()) {

168

number.rightIor()

169

} else {

170

Ior.Both(warnings, number)

171

}

172

} ?: "Invalid number format".nel().leftIor()

173

}

174

175

val results = listOf("007", "42", "abc", "0123").map { parseNumber(it) }

176

// Results:

177

// Both(["Leading zero detected"], 7)

178

// Right(42)

179

// Left(["Invalid number format"])

180

// Both(["Leading zero detected"], 123)

181

```

182

183

### Computation with Warnings

184

185

```kotlin

186

import arrow.core.*

187

188

data class ValidationResult(val warnings: List<String>, val errors: List<String>)

189

190

fun validateUser(name: String, email: String): Ior<List<String>, User> {

191

val warnings = mutableListOf<String>()

192

val errors = mutableListOf<String>()

193

194

// Name validation

195

when {

196

name.isBlank() -> errors.add("Name is required")

197

name.length < 2 -> warnings.add("Name is very short")

198

}

199

200

// Email validation

201

when {

202

email.isBlank() -> errors.add("Email is required")

203

!email.contains('@') -> errors.add("Invalid email format")

204

!email.contains('.') -> warnings.add("Email might be missing domain")

205

}

206

207

return when {

208

errors.isNotEmpty() -> errors.leftIor()

209

warnings.isNotEmpty() -> Ior.Both(warnings, User(name, email))

210

else -> User(name, email).rightIor()

211

}

212

}

213

214

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

215

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

216

217

val warnings = validateUser("Al", "alice@localhost")

218

// Both(["Name is very short", "Email might be missing domain"], User("Al", "alice@localhost"))

219

220

val errors = validateUser("", "invalid")

221

// Left(["Name is required", "Invalid email format"])

222

```

223

224

### Chaining with Context Accumulation

225

226

```kotlin

227

import arrow.core.*

228

229

fun processData(input: String): Ior<List<String>, ProcessedData> {

230

return parseInput(input)

231

.flatMap(::combine) { parsed -> validate(parsed) }

232

.flatMap(::combine) { validated -> transform(validated) }

233

}

234

235

fun combine(warnings1: List<String>, warnings2: List<String>): List<String> =

236

warnings1 + warnings2

237

238

// Each step can add warnings while still producing a result

239

fun parseInput(input: String): Ior<List<String>, ParsedData> = TODO()

240

fun validate(data: ParsedData): Ior<List<String>, ValidatedData> = TODO()

241

fun transform(data: ValidatedData): Ior<List<String>, ProcessedData> = TODO()

242

```

243

244

### Pattern Matching

245

246

```kotlin

247

import arrow.core.*

248

249

fun handleResult(result: Ior<String, Int>): String = result.fold(

250

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

251

fb = { value -> "Success: $value" },

252

fab = { error, value -> "Warning: $error, but got result: $value" }

253

)

254

255

val warning = Ior.Both("deprecation warning", 42)

256

println(handleResult(warning))

257

// "Warning: deprecation warning, but got result: 42"

258

```