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

safe-collections.mddocs/

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