0
# Collection Extensions
1
2
Arrow Core provides functional programming extensions for Kotlin collections, focusing on zip operations, alignment operations, error handling, and utility functions.
3
4
## Capabilities
5
6
### Zip Operations
7
8
Enhanced zip operations that support multiple collections and custom transformations.
9
10
```kotlin { .api }
11
/**
12
* Zip with 3 collections using a transform function
13
*/
14
inline fun <B, C, D, E> Iterable<B>.zip(
15
c: Iterable<C>,
16
d: Iterable<D>,
17
transform: (B, C, D) -> E
18
): List<E>
19
20
/**
21
* Zip with 4 collections using a transform function
22
*/
23
inline fun <B, C, D, E, F> Iterable<B>.zip(
24
c: Iterable<C>,
25
d: Iterable<D>,
26
e: Iterable<E>,
27
transform: (B, C, D, E) -> F
28
): List<F>
29
30
/**
31
* Zip operations available up to 10 collections
32
*/
33
```
34
35
**Usage Examples:**
36
37
```kotlin
38
import arrow.core.*
39
40
// Zip three collections
41
val numbers = listOf(1, 2, 3)
42
val letters = listOf("a", "b", "c")
43
val symbols = listOf("!", "?", ".")
44
val combined = numbers.zip(letters, symbols) { num, letter, symbol ->
45
"$num$letter$symbol"
46
}
47
// ["1a!", "2b?", "3c."]
48
49
// Zip four collections
50
val list1 = listOf(1, 2)
51
val list2 = listOf("a", "b")
52
val list3 = listOf(true, false)
53
val list4 = listOf(10.0, 20.0)
54
val result = list1.zip(list2, list3, list4) { num, str, bool, dbl ->
55
Triple(num + str, bool, dbl)
56
}
57
```
58
59
### Map with Error Accumulation
60
61
Transform collections while accumulating errors instead of short-circuiting on the first error.
62
63
```kotlin { .api }
64
/**
65
* Map over collection with error accumulation using custom error combination
66
*/
67
inline fun <Error, A, B> Iterable<A>.mapOrAccumulate(
68
combine: (Error, Error) -> Error,
69
transform: RaiseAccumulate<Error>.(A) -> B
70
): Either<Error, List<B>>
71
72
/**
73
* Map over collection with error accumulation using NonEmptyList
74
*/
75
inline fun <Error, A, B> Iterable<A>.mapOrAccumulate(
76
transform: RaiseAccumulate<Error>.(A) -> B
77
): Either<NonEmptyList<Error>, List<B>>
78
79
/**
80
* Flatten collection of Either values with error accumulation
81
*/
82
inline fun <Error, A> Iterable<Either<Error, A>>.flattenOrAccumulate(
83
combine: (Error, Error) -> Error
84
): Either<Error, List<A>>
85
86
/**
87
* Flatten collection of Either values to NonEmptyList of errors
88
*/
89
fun <Error, A> Iterable<Either<Error, A>>.flattenOrAccumulate(): Either<NonEmptyList<Error>, List<A>>
90
```
91
92
**Usage Examples:**
93
94
```kotlin
95
import arrow.core.*
96
import arrow.core.raise.*
97
98
// Map with error accumulation
99
val inputs = listOf("1", "2", "invalid1", "4", "invalid2")
100
val results = inputs.mapOrAccumulate { str ->
101
str.toIntOrNull() ?: raise("Invalid number: $str")
102
}
103
// Left(NonEmptyList("Invalid number: invalid1", "Invalid number: invalid2"))
104
105
// Custom error combination
106
val customResults = inputs.mapOrAccumulate(
107
combine = { e1, e2 -> "$e1; $e2" }
108
) { str ->
109
str.toIntOrNull() ?: raise("Invalid: $str")
110
}
111
// Left("Invalid: invalid1; Invalid: invalid2")
112
113
// Flatten Either collection
114
val eithers = listOf(1.right(), "error1".left(), 3.right(), "error2".left())
115
val flattened = eithers.flattenOrAccumulate()
116
// Left(NonEmptyList("error1", "error2"))
117
```
118
119
### Alignment Operations
120
121
Combine collections that may have different lengths, handling missing elements gracefully.
122
123
```kotlin { .api }
124
/**
125
* Align two collections using Ior to represent presence
126
*/
127
fun <A, B> Iterable<A>.align(b: Iterable<B>): List<Ior<A, B>>
128
129
/**
130
* Align and transform two collections
131
*/
132
inline fun <A, B, C> Iterable<A>.align(
133
b: Iterable<B>,
134
fa: (Ior<A, B>) -> C
135
): List<C>
136
137
/**
138
* Pad zip - zip with null padding for missing elements
139
*/
140
fun <A, B> Iterable<A>.padZip(other: Iterable<B>): List<Pair<A?, B?>>
141
142
/**
143
* Pad zip with transformation
144
*/
145
inline fun <A, B, C> Iterable<A>.padZip(
146
other: Iterable<B>,
147
fa: (A?, B?) -> C
148
): List<C>
149
150
/**
151
* Left pad zip - keep all elements from right collection
152
*/
153
fun <A, B> Iterable<A>.leftPadZip(other: Iterable<B>): List<Pair<A?, B>>
154
155
/**
156
* Left pad zip with transformation
157
*/
158
inline fun <A, B, C> Iterable<A>.leftPadZip(
159
other: Iterable<B>,
160
fab: (A?, B) -> C
161
): List<C>
162
163
/**
164
* Right pad zip - keep all elements from left collection
165
*/
166
fun <A, B> Iterable<A>.rightPadZip(other: Iterable<B>): List<Pair<A, B?>>
167
168
/**
169
* Right pad zip with transformation
170
*/
171
inline fun <A, B, C> Iterable<A>.rightPadZip(
172
other: Iterable<B>,
173
fa: (A, B?) -> C
174
): List<C>
175
```
176
177
**Usage Examples:**
178
179
```kotlin
180
import arrow.core.*
181
182
// Align collections of different lengths
183
val left = listOf(1, 2, 3)
184
val right = listOf("a", "b")
185
val aligned = left.align(right)
186
// [Ior.Both(1, "a"), Ior.Both(2, "b"), Ior.Left(3)]
187
188
// Align with transformation
189
val combined = left.align(right) { ior ->
190
ior.fold(
191
{ "Left: $it" },
192
{ "Right: $it" },
193
{ l, r -> "Both: $l, $r" }
194
)
195
}
196
// ["Both: 1, a", "Both: 2, b", "Left: 3"]
197
198
// Pad zip with nulls
199
val padded = listOf(1, 2).padZip(listOf("a"))
200
// [Pair(1, "a"), Pair(2, null)]
201
202
// Left pad zip
203
val leftPadded = listOf(1).leftPadZip(listOf("a", "b"))
204
// [Pair(1, "a"), Pair(null, "b")]
205
206
// Right pad zip
207
val rightPadded = listOf(1, 2).rightPadZip(listOf("a"))
208
// [Pair(1, "a"), Pair(2, null)]
209
```
210
211
### Collection Analysis and Utilities
212
213
Utility functions for collection processing and analysis.
214
215
```kotlin { .api }
216
/**
217
* Split collection into head and tail
218
*/
219
fun <A> Iterable<A>.split(): Pair<List<A>, A>?
220
221
/**
222
* Get tail (all but first element)
223
*/
224
fun <A> Iterable<A>.tail(): List<A>
225
226
/**
227
* Interleave elements with another collection
228
*/
229
fun <A> Iterable<A>.interleave(other: Iterable<A>): List<A>
230
231
/**
232
* Unweave using a function that produces iterables
233
*/
234
fun <A, B> Iterable<A>.unweave(ffa: (A) -> Iterable<B>): List<B>
235
236
/**
237
* Separate Either values into lefts and rights
238
*/
239
fun <A, B> Iterable<Either<A, B>>.separateEither(): Pair<List<A>, List<B>>
240
241
/**
242
* Separate Either values with transformation
243
*/
244
inline fun <T, A, B> Iterable<T>.separateEither(
245
f: (T) -> Either<A, B>
246
): Pair<List<A>, List<B>>
247
248
/**
249
* Separate Ior values into components
250
*/
251
fun <A, B> Iterable<Ior<A, B>>.separateIor(): Pair<List<A>, List<B>>
252
253
/**
254
* Unalign Ior values with nulls for missing sides
255
*/
256
fun <A, B> Iterable<Ior<A, B>>.unalign(): Pair<List<A?>, List<B?>>
257
258
/**
259
* Unalign with transformation to Ior
260
*/
261
inline fun <A, B, C> Iterable<C>.unalign(
262
fa: (C) -> Ior<A, B>
263
): Pair<List<A?>, List<B?>>
264
```
265
266
**Usage Examples:**
267
268
```kotlin
269
import arrow.core.*
270
271
// Split into head and tail
272
val numbers = listOf(1, 2, 3, 4)
273
val split = numbers.split()
274
// Pair([2, 3, 4], 1)
275
276
// Interleave collections
277
val evens = listOf(2, 4, 6)
278
val odds = listOf(1, 3, 5, 7)
279
val interleaved = evens.interleave(odds)
280
// [2, 1, 4, 3, 6, 5, 7]
281
282
// Separate Either values
283
val eithers = listOf(1.left(), 2.right(), 3.left(), 4.right())
284
val (lefts, rights) = eithers.separateEither()
285
// lefts: [1, 3], rights: [2, 4]
286
287
// Separate with transformation
288
val numbers2 = listOf(1, 2, 3, 4, 5, 6)
289
val (evens2, odds2) = numbers2.separateEither { num ->
290
if (num % 2 == 0) num.left() else num.right()
291
}
292
// evens2: [2, 4, 6], odds2: [1, 3, 5]
293
294
// Separate Ior values
295
val iors = listOf(
296
Ior.Both("a", 1),
297
Ior.Left("b"),
298
Ior.Right(2)
299
)
300
val (leftValues, rightValues) = iors.separateIor()
301
// leftValues: ["a", "b"], rightValues: [1, 2]
302
```
303
304
### Safe Element Access
305
306
Safe alternatives to collection access operations that return Option instead of throwing exceptions.
307
308
```kotlin { .api }
309
/**
310
* Get first element safely
311
*/
312
fun <T> Iterable<T>.firstOrNone(): Option<T>
313
314
/**
315
* Get first element matching predicate safely
316
*/
317
inline fun <T> Iterable<T>.firstOrNone(predicate: (T) -> Boolean): Option<T>
318
319
/**
320
* Get single element safely
321
*/
322
fun <T> Iterable<T>.singleOrNone(): Option<T>
323
324
/**
325
* Get single element matching predicate safely
326
*/
327
inline fun <T> Iterable<T>.singleOrNone(predicate: (T) -> Boolean): Option<T>
328
329
/**
330
* Get last element safely
331
*/
332
fun <T> Iterable<T>.lastOrNone(): Option<T>
333
334
/**
335
* Get last element matching predicate safely
336
*/
337
inline fun <T> Iterable<T>.lastOrNone(predicate: (T) -> Boolean): Option<T>
338
339
/**
340
* Get element at index safely
341
*/
342
fun <T> Iterable<T>.elementAtOrNone(index: Int): Option<T>
343
```
344
345
**Usage Examples:**
346
347
```kotlin
348
import arrow.core.*
349
350
// Safe first element access
351
val numbers = listOf(1, 2, 3)
352
val first = numbers.firstOrNone() // Some(1)
353
val empty = emptyList<Int>().firstOrNone() // None
354
355
// Safe element access with predicate
356
val even = numbers.firstOrNone { it % 2 == 0 } // Some(2)
357
val notFound = numbers.firstOrNone { it > 10 } // None
358
359
// Safe single element access
360
val single = listOf(42).singleOrNone() // Some(42)
361
val multiple = listOf(1, 2).singleOrNone() // None
362
363
// Safe element at index
364
val atIndex = numbers.elementAtOrNone(1) // Some(2)
365
val outOfBounds = numbers.elementAtOrNone(10) // None
366
```
367
368
### Reduce Operations
369
370
Enhanced reduce operations with proper handling of empty collections.
371
372
```kotlin { .api }
373
/**
374
* Reduce with initial value function, returns null for empty collections
375
*/
376
inline fun <A, B> Iterable<A>.reduceOrNull(
377
initial: (A) -> B,
378
operation: (acc: B, A) -> B
379
): B?
380
381
/**
382
* Right reduce with proper handling of empty collections
383
*/
384
inline fun <A, B> List<A>.reduceRightNull(
385
initial: (A) -> B,
386
operation: (A, acc: B) -> B
387
): B?
388
```
389
390
### Option and Comparison Utilities
391
392
```kotlin { .api }
393
/**
394
* Filter Option values, keeping only Some values
395
*/
396
fun <T> Iterable<Option<T>>.filterOption(): List<T>
397
398
/**
399
* Alias for filterOption
400
*/
401
fun <T> Iterable<Option<T>>.flattenOption(): List<T>
402
403
/**
404
* Prepend element to iterable
405
*/
406
infix fun <T> T.prependTo(list: Iterable<T>): List<T>
407
408
/**
409
* Compare iterables lexicographically
410
*/
411
operator fun <A : Comparable<A>> Iterable<A>.compareTo(other: Iterable<A>): Int
412
413
/**
414
* Crosswalk operation
415
*/
416
fun <A, B> Iterable<A>.crosswalk(f: (A) -> Iterable<B>): List<List<B>>
417
418
/**
419
* Crosswalk with Map result
420
*/
421
fun <A, K, V> Iterable<A>.crosswalkMap(f: (A) -> Map<K, V>): Map<K, List<V>>
422
423
/**
424
* Crosswalk with nullable result
425
*/
426
fun <A, B> Iterable<A>.crosswalkNull(f: (A) -> B?): List<B>?
427
428
/**
429
* Flatten nested iterables
430
*/
431
fun <A> Iterable<Iterable<A>>.flatten(): List<A>
432
```
433
434
## Sequence Extensions
435
436
Arrow Core also provides similar extensions for Sequence with lazy evaluation.
437
438
### Sequence Zip Operations
439
440
```kotlin { .api }
441
/**
442
* Zip sequences with 3-10 parameters, maintaining lazy evaluation
443
*/
444
fun <B, C, D, E> Sequence<B>.zip(
445
c: Sequence<C>,
446
d: Sequence<D>,
447
map: (B, C, D) -> E
448
): Sequence<E>
449
```
450
451
### Sequence Alignment and Padding
452
453
```kotlin { .api }
454
/**
455
* Align two sequences with Ior
456
*/
457
fun <A, B> Sequence<A>.align(seqB: Sequence<B>): Sequence<Ior<A, B>>
458
459
/**
460
* Pad zip sequences with nulls
461
*/
462
fun <A, B> Sequence<A>.padZip(other: Sequence<B>): Sequence<Pair<A?, B?>>
463
464
/**
465
* Left pad zip sequences
466
*/
467
fun <A, B> Sequence<A>.leftPadZip(other: Sequence<B>): Sequence<Pair<A?, B>>
468
469
/**
470
* Right pad zip sequences
471
*/
472
fun <A, B> Sequence<A>.rightPadZip(other: Sequence<B>): Sequence<Pair<A, B?>>
473
```
474
475
### Sequence Utilities
476
477
```kotlin { .api }
478
/**
479
* Split sequence into head and tail
480
*/
481
fun <A> Sequence<A>.split(): Pair<Sequence<A>, A>?
482
483
/**
484
* Interleave sequences
485
*/
486
fun <A> Sequence<A>.interleave(other: Sequence<A>): Sequence<A>
487
488
/**
489
* Filter Option values in sequence
490
*/
491
fun <A> Sequence<Option<A>>.filterOption(): Sequence<A>
492
493
/**
494
* Separate Either values in sequence
495
*/
496
fun <A, B> Sequence<Either<A, B>>.separateEither(): Pair<List<A>, List<B>>
497
498
/**
499
* Map with error accumulation for sequences
500
*/
501
fun <Error, A, B> Sequence<A>.mapOrAccumulate(
502
combine: (Error, Error) -> Error,
503
transform: RaiseAccumulate<Error>.(A) -> B
504
): Either<Error, List<B>>
505
```
506
507
## Map Extensions
508
509
Enhanced operations for Map collections.
510
511
### Map Zip Operations
512
513
```kotlin { .api }
514
/**
515
* Zip maps by key intersection
516
*/
517
fun <K, A, B> Map<K, A>.zip(other: Map<K, B>): Map<K, Pair<A, B>>
518
519
/**
520
* Zip maps with transformation (up to 10 maps)
521
*/
522
inline fun <Key, A, B, C> Map<Key, A>.zip(
523
other: Map<Key, B>,
524
map: (Key, A, B) -> C
525
): Map<Key, C>
526
```
527
528
### Map Alignment and Padding
529
530
```kotlin { .api }
531
/**
532
* Align maps using Ior
533
*/
534
fun <K, A, B> Map<K, A>.align(b: Map<K, B>): Map<K, Ior<A, B>>
535
536
/**
537
* Pad zip maps
538
*/
539
fun <K, A, B> Map<K, A>.padZip(other: Map<K, B>): Map<K, Pair<A?, B?>>
540
541
/**
542
* Structural align with combination function
543
*/
544
fun <K, A> Map<K, A>.salign(
545
other: Map<K, A>,
546
combine: (A, A) -> A
547
): Map<K, A>
548
```
549
550
### Map Utilities
551
552
```kotlin { .api }
553
/**
554
* FlatMap values keeping only matching keys
555
*/
556
fun <K, A, B> Map<K, A>.flatMapValues(f: (Map.Entry<K, A>) -> Map<K, B>): Map<K, B>
557
558
/**
559
* Map values with error accumulation
560
*/
561
inline fun <K, E, A, B> Map<K, A>.mapValuesOrAccumulate(
562
combine: (E, E) -> E,
563
transform: RaiseAccumulate<E>.(Map.Entry<K, A>) -> B
564
): Either<E, Map<K, B>>
565
566
/**
567
* Safe get operation returning Option
568
*/
569
fun <K, V> Map<K, V>.getOrNone(key: K): Option<V>
570
571
/**
572
* Combine maps with function for conflicting keys
573
*/
574
fun <K, A> Map<K, A>.combine(
575
other: Map<K, A>,
576
combine: (A, A) -> A
577
): Map<K, A>
578
579
/**
580
* Filter Option values in map
581
*/
582
fun <K, A> Map<K, Option<A>>.filterOption(): Map<K, A>
583
584
/**
585
* Unalign map of Ior values
586
*/
587
fun <K, A, B> Map<K, Ior<A, B>>.unalign(): Pair<Map<K, A>, Map<K, B>>
588
589
/**
590
* Unzip map of pairs
591
*/
592
fun <K, A, B> Map<K, Pair<A, B>>.unzip(): Pair<Map<K, A>, Map<K, B>>
593
```
594
595
**Usage Examples:**
596
597
```kotlin
598
import arrow.core.*
599
600
// Map zip by intersection
601
val map1 = mapOf(1 to "a", 2 to "b", 3 to "c")
602
val map2 = mapOf(1 to 10, 2 to 20, 4 to 40)
603
val zipped = map1.zip(map2)
604
// {1=(a, 10), 2=(b, 20)}
605
606
// Map alignment
607
val aligned = map1.align(map2)
608
// {1=Ior.Both(a, 10), 2=Ior.Both(b, 20), 3=Ior.Left(c), 4=Ior.Right(40)}
609
610
// Safe get
611
val value = map1.getOrNone(1) // Some("a")
612
val missing = map1.getOrNone(5) // None
613
614
// Combine maps
615
val combined = map1.combine(map2) { str, num -> "$str$num" }
616
// Only works if both maps have same value types
617
```