0
# Inclusive OR with Ior
1
2
Represents values that can be either one type, another type, or both simultaneously. Ior (Inclusive OR) is useful for computations that can produce partial results alongside errors or warnings, allowing you to accumulate information from both success and failure paths.
3
4
## Capabilities
5
6
### Ior Construction
7
8
Create Ior instances representing left values, right values, or both.
9
10
```kotlin { .api }
11
/**
12
* Ior containing only a left value (typically error/warning)
13
*/
14
data class Left<out A>(val value: A) : Ior<A, Nothing>
15
16
/**
17
* Ior containing only a right value (typically success)
18
*/
19
data class Right<out B>(val value: B) : Ior<Nothing, B>
20
21
/**
22
* Ior containing both left and right values
23
*/
24
data class Both<out A, out B>(val left: A, val right: B) : Ior<A, B>
25
```
26
27
### Ior Extension Functions
28
29
Convenience functions for creating Ior instances.
30
31
```kotlin { .api }
32
/**
33
* Wrap any value in Ior.Left
34
*/
35
fun <A> A.leftIor(): Ior<A, Nothing>
36
37
/**
38
* Wrap any value in Ior.Right
39
*/
40
fun <A> A.rightIor(): Ior<Nothing, A>
41
42
/**
43
* Create Ior.Both from two values
44
*/
45
fun <A, B> bothIor(left: A, right: B): Ior<A, B>
46
```
47
48
**Usage Examples:**
49
50
```kotlin
51
// Direct construction
52
val error: Ior<String, Int> = Ior.Left("Error occurred")
53
val success: Ior<String, Int> = Ior.Right(42)
54
val partial: Ior<String, Int> = Ior.Both("Warning: partial result", 42)
55
56
// Using extension functions
57
val warning = "Deprecated API".leftIor()
58
val result = 100.rightIor()
59
val combined = bothIor("Processing with warnings", listOf(1, 2, 3))
60
```
61
62
### Ior Type Guards
63
64
Check the state of Ior instances with type-safe guards.
65
66
```kotlin { .api }
67
/**
68
* Check if Ior contains only left value
69
* @return true if Left, false otherwise
70
*/
71
fun <A, B> Ior<A, B>.isLeft(): Boolean
72
73
/**
74
* Check if Ior contains only right value
75
* @return true if Right, false otherwise
76
*/
77
fun <A, B> Ior<A, B>.isRight(): Boolean
78
79
/**
80
* Check if Ior contains both left and right values
81
* @return true if Both, false otherwise
82
*/
83
fun <A, B> Ior<A, B>.isBoth(): Boolean
84
```
85
86
### Ior Pattern Matching
87
88
Safely extract values or perform operations based on Ior state.
89
90
```kotlin { .api }
91
/**
92
* Pattern match on Ior, providing handlers for all three cases
93
*/
94
fun <A, B, C> Ior<A, B>.fold(
95
ifLeft: (A) -> C,
96
ifRight: (B) -> C,
97
ifBoth: (A, B) -> C
98
): C
99
100
/**
101
* Extract values into a pair of optionals
102
*/
103
fun <A, B> Ior<A, B>.pad(): Pair<A?, B?>
104
105
/**
106
* Unwrap into Either<Either<A, B>, Pair<A, B>>
107
*/
108
fun <A, B> Ior<A, B>.unwrap(): Either<Either<A, B>, Pair<A, B>>
109
```
110
111
**Usage Examples:**
112
113
```kotlin
114
val result: Ior<String, Int> = Ior.Both("Warning", 42)
115
116
// Pattern matching
117
val message = result.fold(
118
ifLeft = { error -> "Error: $error" },
119
ifRight = { value -> "Success: $value" },
120
ifBoth = { warning, value -> "Warning: $warning, Result: $value" }
121
)
122
123
// Extract as optional pair
124
val (maybeWarning, maybeResult) = result.pad() // ("Warning", 42)
125
126
// Check state
127
val hasBoth = result.isBoth() // true
128
val hasWarning = result.isLeft() || result.isBoth() // true
129
```
130
131
### Ior Transformations
132
133
Transform Ior values while preserving the container structure.
134
135
```kotlin { .api }
136
/**
137
* Transform the right value if present
138
*/
139
fun <A, B, C> Ior<A, B>.map(f: (B) -> C): Ior<A, C>
140
141
/**
142
* Transform the left value if present
143
*/
144
fun <A, B, C> Ior<A, B>.mapLeft(f: (A) -> C): Ior<C, B>
145
146
/**
147
* Transform both values if both are present
148
*/
149
fun <A, B, C, D> Ior<A, B>.bimap(
150
leftMap: (A) -> C,
151
rightMap: (B) -> D
152
): Ior<C, D>
153
154
/**
155
* Swap left and right positions
156
*/
157
fun <A, B> Ior<A, B>.swap(): Ior<B, A>
158
```
159
160
**Usage Examples:**
161
162
```kotlin
163
val processing: Ior<List<String>, List<Int>> = Ior.Both(
164
listOf("Warning: deprecated field"),
165
listOf(1, 2, 3)
166
)
167
168
// Transform right values
169
val doubled = processing.map { numbers -> numbers.map { it * 2 } }
170
// Result: Ior.Both(["Warning: deprecated field"], [2, 4, 6])
171
172
// Transform left values
173
val formatted = processing.mapLeft { warnings ->
174
warnings.map { "⚠️ $it" }
175
}
176
177
// Transform both sides
178
val processed = processing.bimap(
179
leftMap = { warnings -> warnings.size },
180
rightMap = { numbers -> numbers.sum() }
181
)
182
// Result: Ior.Both(1, 6)
183
```
184
185
### Ior Monadic Operations
186
187
Chain Ior operations with proper left value combination.
188
189
```kotlin { .api }
190
/**
191
* Monadic bind with left value combination
192
*/
193
fun <A, B, C> Ior<A, B>.flatMap(
194
combine: (A, A) -> A,
195
f: (B) -> Ior<A, C>
196
): Ior<A, C>
197
```
198
199
**Usage Examples:**
200
201
```kotlin
202
fun processStep1(input: String): Ior<List<String>, Int> {
203
val warnings = mutableListOf<String>()
204
val result = input.length
205
206
if (input.contains("deprecated")) {
207
warnings.add("Using deprecated input format")
208
}
209
210
return if (warnings.isEmpty()) {
211
Ior.Right(result)
212
} else {
213
Ior.Both(warnings, result)
214
}
215
}
216
217
fun processStep2(value: Int): Ior<List<String>, String> {
218
val warnings = mutableListOf<String>()
219
val result = "Processed: $value"
220
221
if (value > 100) {
222
warnings.add("Large value detected")
223
}
224
225
return if (warnings.isEmpty()) {
226
Ior.Right(result)
227
} else {
228
Ior.Both(warnings, result)
229
}
230
}
231
232
// Chain operations, combining warnings
233
val pipeline = processStep1("deprecated_input_with_long_name")
234
.flatMap(
235
combine = { w1, w2 -> w1 + w2 } // Combine warning lists
236
) { value -> processStep2(value) }
237
238
// Result: Ior.Both(
239
// ["Using deprecated input format", "Large value detected"],
240
// "Processed: 26"
241
// )
242
```
243
244
### Ior Conversions
245
246
Convert Ior to other Arrow types.
247
248
```kotlin { .api }
249
/**
250
* Convert Ior to Either, losing Both case (Right wins)
251
*/
252
fun <A, B> Ior<A, B>.toEither(): Either<A, B>
253
254
/**
255
* Convert Ior to Option, keeping only Right values
256
*/
257
fun <A, B> Ior<A, B>.toOption(): Option<B>
258
259
/**
260
* Convert Ior to nullable, keeping only Right values
261
*/
262
fun <A, B> Ior<A, B>.orNull(): B?
263
```
264
265
**Usage Examples:**
266
267
```kotlin
268
val both: Ior<String, Int> = Ior.Both("warning", 42)
269
val left: Ior<String, Int> = Ior.Left("error")
270
val right: Ior<String, Int> = Ior.Right(100)
271
272
// Convert to Either (Right wins in Both case)
273
val eitherFromBoth = both.toEither() // Either.Right(42)
274
val eitherFromLeft = left.toEither() // Either.Left("error")
275
val eitherFromRight = right.toEither() // Either.Right(100)
276
277
// Convert to Option (only Right values)
278
val optionFromBoth = both.toOption() // Some(42)
279
val optionFromLeft = left.toOption() // None
280
val optionFromRight = right.toOption() // Some(100)
281
```
282
283
### Ior Combination
284
285
Combine multiple Ior values with left value accumulation.
286
287
```kotlin { .api }
288
/**
289
* Zip two Ior values with left combination function
290
*/
291
fun <A, B, C, D> Ior<A, B>.zip(
292
combine: (A, A) -> A,
293
other: Ior<A, C>,
294
f: (B, C) -> D
295
): Ior<A, D>
296
297
/**
298
* Apply function if both sides have right values
299
*/
300
fun <A, B, C> Ior<A, B>.ap(
301
combine: (A, A) -> A,
302
f: Ior<A, (B) -> C>
303
): Ior<A, C>
304
```
305
306
**Usage Examples:**
307
308
```kotlin
309
val result1: Ior<List<String>, Int> = Ior.Both(listOf("warning1"), 10)
310
val result2: Ior<List<String>, Int> = Ior.Both(listOf("warning2"), 20)
311
312
// Combine with warning accumulation
313
val combined = result1.zip(
314
combine = { w1, w2 -> w1 + w2 }, // Combine warning lists
315
other = result2
316
) { a, b -> a + b }
317
318
// Result: Ior.Both(["warning1", "warning2"], 30)
319
```
320
321
## Ior Use Cases
322
323
### Logging and Computation
324
325
Accumulate logs/warnings while computing results.
326
327
```kotlin
328
fun processData(data: List<String>): Ior<List<String>, List<Int>> {
329
val warnings = mutableListOf<String>()
330
val results = mutableListOf<Int>()
331
332
for (item in data) {
333
when {
334
item.isBlank() -> warnings.add("Empty item skipped")
335
item.startsWith("old_") -> {
336
warnings.add("Legacy format detected: $item")
337
results.add(item.removePrefix("old_").length)
338
}
339
else -> results.add(item.length)
340
}
341
}
342
343
return when {
344
warnings.isEmpty() -> Ior.Right(results)
345
results.isEmpty() -> Ior.Left(warnings)
346
else -> Ior.Both(warnings, results)
347
}
348
}
349
```
350
351
### Partial Validation
352
353
Validate data while collecting all validation errors.
354
355
```kotlin
356
data class ValidationResult<A>(val warnings: List<String>, val value: A)
357
358
fun validateUser(input: UserInput): Ior<List<String>, User> {
359
val warnings = mutableListOf<String>()
360
361
val name = if (input.name.isNotBlank()) {
362
input.name
363
} else {
364
warnings.add("Name should not be empty")
365
"Unknown"
366
}
367
368
val email = if (input.email.contains("@")) {
369
input.email
370
} else {
371
warnings.add("Invalid email format")
372
"unknown@example.com"
373
}
374
375
val user = User(name, email)
376
377
return if (warnings.isEmpty()) {
378
Ior.Right(user)
379
} else {
380
Ior.Both(warnings, user)
381
}
382
}
383
```
384
385
## Types
386
387
### IorNel Type Alias
388
389
```kotlin { .api }
390
/**
391
* Ior with NonEmptyList for error accumulation
392
*/
393
typealias IorNel<E, A> = Ior<NonEmptyList<E>, A>
394
```