0
# Safe Collections
1
2
NonEmptyList provides collections guaranteed to have at least one element, eliminating the possibility of empty collection errors and enabling safe head/tail operations. It implements the List interface while ensuring non-emptiness.
3
4
## Core Types
5
6
```kotlin { .api }
7
// Value class for performance
8
value class NonEmptyList<out E>(val all: List<E>) : List<E>, NonEmptyCollection<E>
9
10
// Type alias
11
typealias Nel<A> = NonEmptyList<A>
12
```
13
14
## Construction
15
16
```kotlin { .api }
17
// Primary constructor
18
NonEmptyList(head: E, tail: List<E>)
19
20
// From varargs
21
fun <T> nonEmptyListOf(head: T, vararg tail: T): NonEmptyList<T>
22
23
// Safe conversion from List
24
fun <T> List<T>.toNonEmptyListOrNull(): NonEmptyList<T>?
25
26
// From single element
27
fun <A> A.nel(): NonEmptyList<A>
28
```
29
30
## Safe Access
31
32
```kotlin { .api }
33
// Always safe - never throws
34
val head: E
35
val tail: List<E>
36
37
// Convert back to regular List
38
fun toList(): List<E>
39
40
// Always returns false
41
override fun isEmpty(): Boolean
42
43
// Safe last element (never null)
44
override fun lastOrNull(): E
45
```
46
47
## Transformation
48
49
```kotlin { .api }
50
// Transform elements (preserves non-emptiness)
51
override fun <T> map(transform: (E) -> T): NonEmptyList<T>
52
53
// Monadic bind
54
override fun <T> flatMap(transform: (E) -> NonEmptyCollection<T>): NonEmptyList<T>
55
56
// Transform with index
57
override fun <T> mapIndexed(transform: (index: Int, E) -> T): NonEmptyList<T>
58
59
// Remove duplicates
60
override fun distinct(): NonEmptyList<E>
61
override fun <K> distinctBy(selector: (E) -> K): NonEmptyList<E>
62
```
63
64
## Combining Lists
65
66
```kotlin { .api }
67
// Concatenation (always non-empty result)
68
operator fun plus(l: NonEmptyList<E>): NonEmptyList<E>
69
override operator fun plus(elements: Iterable<E>): NonEmptyList<E>
70
override operator fun plus(element: E): NonEmptyList<E>
71
```
72
73
## Folding Operations
74
75
```kotlin { .api }
76
// Left fold with guaranteed execution (AT_LEAST_ONCE)
77
fun <Acc> foldLeft(b: Acc, f: (Acc, E) -> Acc): Acc
78
```
79
80
## Comonadic Operations
81
82
```kotlin { .api }
83
// Comonadic operations
84
fun <T> coflatMap(f: (NonEmptyList<E>) -> T): NonEmptyList<T>
85
fun extract(): E // Returns head
86
```
87
88
## Zipping Operations
89
90
```kotlin { .api }
91
// Zip two NonEmptyLists
92
fun <T> zip(other: NonEmptyList<T>): NonEmptyList<Pair<E, T>>
93
94
// Zip with transform function (2-5 arity overloads)
95
fun <B, Z> zip(
96
b: NonEmptyList<B>,
97
map: (E, B) -> Z
98
): NonEmptyList<Z>
99
100
// Zip with padding (handles different lengths)
101
fun <T> padZip(other: NonEmptyList<T>): NonEmptyList<Pair<E?, T?>>
102
fun <B, C> padZip(
103
other: NonEmptyList<B>,
104
left: (E) -> C,
105
right: (B) -> C,
106
both: (E, B) -> C
107
): NonEmptyList<C>
108
```
109
110
## Alignment Operations
111
112
```kotlin { .api }
113
// Align with another list using Ior
114
fun <T> align(other: NonEmptyList<T>): NonEmptyList<Ior<E, T>>
115
```
116
117
## Usage Examples
118
119
### Basic Construction and Access
120
121
```kotlin
122
import arrow.core.*
123
124
// Create NonEmptyList
125
val users = nonEmptyListOf("Alice", "Bob", "Charlie")
126
val singleUser = "Alice".nel()
127
128
// Safe access (never throws)
129
println(users.head) // "Alice"
130
println(users.tail) // ["Bob", "Charlie"]
131
132
// Convert to regular List when needed
133
val regularList: List<String> = users.toList()
134
```
135
136
### Safe Operations
137
138
```kotlin
139
import arrow.core.*
140
141
// Transform elements
142
val lengths = users.map { it.length } // NonEmptyList(5, 3, 7)
143
144
// Filter-like operations with safe conversion
145
val longNames = users.filter { it.length > 4 }.toNonEmptyListOrNull()
146
// Option: Some(NonEmptyList("Alice", "Charlie")) or None if empty
147
148
// Chain operations safely
149
val processed = users
150
.map { it.uppercase() }
151
.map { "Hello, $it!" }
152
.distinct()
153
```
154
155
### Combining Collections
156
157
```kotlin
158
import arrow.core.*
159
160
val moreUsers = nonEmptyListOf("David", "Eve")
161
val allUsers = users + moreUsers // NonEmptyList("Alice", "Bob", "Charlie", "David", "Eve")
162
163
// Add single elements
164
val withNewUser = users + "Frank" // NonEmptyList("Alice", "Bob", "Charlie", "Frank")
165
```
166
167
### Folding Operations
168
169
```kotlin
170
import arrow.core.*
171
172
val numbers = nonEmptyListOf(1, 2, 3, 4, 5)
173
174
// Left fold - guaranteed to execute at least once
175
val sum = numbers.foldLeft(0) { acc, n -> acc + n } // 15
176
177
// Build strings
178
val concatenated = users.foldLeft("Users: ") { acc, user -> "$acc$user, " }
179
// "Users: Alice, Bob, Charlie, "
180
```
181
182
### Zipping Operations
183
184
```kotlin
185
import arrow.core.*
186
187
val names = nonEmptyListOf("Alice", "Bob", "Charlie")
188
val ages = nonEmptyListOf(25, 30, 35)
189
val cities = nonEmptyListOf("NYC", "SF")
190
191
// Zip two lists (truncates to shorter length)
192
val pairs = names.zip(ages) // NonEmptyList(("Alice", 25), ("Bob", 30), ("Charlie", 35))
193
194
// Zip with transform
195
val descriptions = names.zip(ages) { name, age -> "$name is $age years old" }
196
// NonEmptyList("Alice is 25 years old", "Bob is 30 years old", ...)
197
198
// Pad zip (handles different lengths)
199
val padded = names.padZip(cities)
200
// NonEmptyList(("Alice", "NYC"), ("Bob", "SF"), ("Charlie", null))
201
```
202
203
### Advanced Operations
204
205
```kotlin
206
import arrow.core.*
207
208
// Comonadic operations
209
val duplicated = names.coflatMap { nel ->
210
"${nel.head} (from list of ${nel.size})"
211
}
212
// NonEmptyList("Alice (from list of 3)", "Bob (from list of 2)", "Charlie (from list of 1)")
213
214
// Alignment with Ior
215
val aligned = names.align(ages)
216
// Each element is Ior<String, Int> representing the alignment
217
```
218
219
### Safe Conversion Patterns
220
221
```kotlin
222
import arrow.core.*
223
224
// Safe conversion from List
225
fun processUsers(userList: List<String>): String =
226
userList.toNonEmptyListOrNull()
227
?.let { nel -> "Processing ${nel.size} users starting with ${nel.head}" }
228
?: "No users to process"
229
230
val emptyResult = processUsers(emptyList()) // "No users to process"
231
val validResult = processUsers(listOf("Alice")) // "Processing 1 users starting with Alice"
232
```
233
234
### Integration with Other Arrow Types
235
236
```kotlin
237
import arrow.core.*
238
239
// With Option
240
val firstLongName: Option<String> = users
241
.filter { it.length > 4 }
242
.toNonEmptyListOrNull()
243
.toOption()
244
.map { it.head }
245
246
// With Either for validation
247
fun validateUsers(input: List<String>): Either<String, NonEmptyList<String>> =
248
input.toNonEmptyListOrNull()
249
?.right()
250
?: "User list cannot be empty".left()
251
```
252
253
### Performance Considerations
254
255
```kotlin
256
import arrow.core.*
257
258
// NonEmptyList is a value class - no runtime overhead
259
val efficient = nonEmptyListOf(1, 2, 3) // Wraps List<Int> at compile time
260
261
// Delegation to List operations when possible
262
val sorted = efficient.sorted() // Uses List.sorted() internally
263
val reversed = efficient.reversed() // Uses List.reversed() internally
264
```
265
266
## NonEmptySet Extensions
267
268
NonEmptySet provides similar functionality to NonEmptyList but ensures uniqueness of elements.
269
270
### NonEmptySet Construction
271
272
```kotlin { .api }
273
// Primary constructor
274
NonEmptySet(first: E, rest: Iterable<E>)
275
276
// From varargs
277
fun <T> nonEmptySetOf(first: T, vararg rest: T): NonEmptySet<T>
278
279
// Safe conversion from Iterable
280
fun <T> Iterable<T>.toNonEmptySetOrNull(): NonEmptySet<T>?
281
fun <T> Iterable<T>.toNonEmptySetOrNone(): Option<NonEmptySet<T>>
282
fun <T> Iterable<T>.toNonEmptySetOrThrow(): NonEmptySet<T>
283
284
// Performance optimized wrapping (unsafe - for performance)
285
fun <T> Set<T>.wrapAsNonEmptySetOrNull(): NonEmptySet<T>?
286
fun <T> Set<T>.wrapAsNonEmptySetOrThrow(): NonEmptySet<T>
287
```
288
289
### NonEmptySet Error Accumulation
290
291
```kotlin { .api }
292
// Error accumulation with custom combine function
293
fun <Error, E, T> NonEmptySet<E>.mapOrAccumulate(
294
combine: (Error, Error) -> Error,
295
transform: RaiseAccumulate<Error>.(E) -> T
296
): Either<Error, NonEmptySet<T>>
297
298
// Error accumulation with NonEmptyList errors
299
fun <Error, E, T> NonEmptySet<E>.mapOrAccumulate(
300
transform: RaiseAccumulate<Error>.(E) -> T
301
): Either<NonEmptyList<Error>, NonEmptySet<T>>
302
```
303
304
### NonEmptySet Usage Examples
305
306
```kotlin
307
import arrow.core.*
308
309
// Create unique collections
310
val duplicateList = listOf("A", "B", "A", "C", "B")
311
val uniqueSet = duplicateList.toNonEmptySetOrNull() // NonEmptySet("A", "B", "C")
312
313
// Safe conversion patterns
314
val maybeUniqueData = inputData.toNonEmptySetOrNone()
315
when (maybeUniqueData) {
316
is Some -> println("Found ${maybeUniqueData.value.size} unique items")
317
is None -> println("No data provided")
318
}
319
320
// Convert between types
321
val fromList = nonEmptyListOf(1, 2, 3, 2, 1)
322
val asSet = fromList.toNonEmptySet() // NonEmptySet(1, 2, 3)
323
val backToList = asSet.toNonEmptyList() // NonEmptyList(1, 2, 3) - order may vary
324
325
// Error accumulation example
326
import arrow.core.raise.*
327
328
data class ValidationError(val field: String, val message: String)
329
330
fun Raise<ValidationError>.validateEmail(email: String): String {
331
ensure(email.contains('@')) { ValidationError("email", "Invalid format") }
332
return email
333
}
334
335
val emails = nonEmptySetOf("alice@test.com", "invalid-email", "bob@test.com")
336
val validatedEmails = emails.mapOrAccumulate { email ->
337
validateEmail(email)
338
}
339
340
when (validatedEmails) {
341
is Either.Right -> println("All emails valid: ${validatedEmails.value}")
342
is Either.Left -> println("Validation errors: ${validatedEmails.value}")
343
}
344
```
345