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
```