NonEmptyList
NonEmptyList[T] is a persistent immutable list with at least one element. The non-empty invariant is enforced at construction time, so head, last, and reduce are total: they return T directly, not Option[T].
from pyrula import NonEmptyList, Some, Nothing
nel = NonEmptyList(1, 2, 3)nel.head() # 1 (always a value)nel.last() # 3 (always a value)nel.length() # 3Construction
Section titled “Construction”Pass at least one argument. The first positional argument is head; the rest are the tail.
NonEmptyList(42) # single-elementNonEmptyList(1, 2, 3) # three elementsNonEmptyList.of(1, 2, 3) # same as the constructorZero arguments raise TypeError at construction time.
From a plain list or IList
Section titled “From a plain list or IList”When the input may be empty, use from_list. It returns Option[NonEmptyList[T]], making the boundary explicit.
from pyrula import NonEmptyList, IList, Some, Nothing
NonEmptyList.from_list([1, 2, 3]) # Some(NonEmptyList(1, 2, 3))NonEmptyList.from_list([]) # Nothing
ilist = IList([10, 20])NonEmptyList.from_list(ilist) # Some(NonEmptyList(10, 20))Total operations
Section titled “Total operations”Because the list is guaranteed non-empty, these operations always succeed:
| Operation | Returns | Notes |
|---|---|---|
head() | T | First element |
last() | T | Last element |
reduce(f) | T | Left-fold seeded by head, no initial value needed |
nel = NonEmptyList(1, 2, 3, 4)nel.head() # 1nel.last() # 4nel.reduce(lambda a, b: a + b) # 10Tail and fold
Section titled “Tail and fold”tail() returns an IList (which may be empty) of all elements after the head.
fold_left is a standard left fold with an explicit initial accumulator.
nel = NonEmptyList(10, 20, 30)nel.tail() # IList([20, 30])nel.fold_left(0, lambda acc, x: acc + x) # 60Transformations that stay non-empty
Section titled “Transformations that stay non-empty”map, flat_map, append, and prepend all return a NonEmptyList. The non-empty guarantee is preserved through transformation.
NonEmptyList(1, 2, 3).map(lambda x: x * 10)# NonEmptyList(10, 20, 30)
NonEmptyList(1, 2).append(3)# NonEmptyList(1, 2, 3)
NonEmptyList(2, 3).prepend(1)# NonEmptyList(1, 2, 3)flat_map expects a function that returns a NonEmptyList. The results are concatenated.
NonEmptyList(1, 2).flat_map(lambda x: NonEmptyList(x, x * 10))# NonEmptyList(1, 10, 2, 20)Conversion
Section titled “Conversion”nel = NonEmptyList(1, 2, 3)nel.to_list() # [1, 2, 3]nel.to_ilist() # IList([1, 2, 3])list(nel) # [1, 2, 3] (iterable)len(nel) # 3Equality
Section titled “Equality”Two NonEmptyList values are equal when they have the same length and every element is equal.
NonEmptyList(1, 2) == NonEmptyList(1, 2) # TrueNonEmptyList(1, 2) == NonEmptyList(1, 3) # FalseScala equivalent
Section titled “Scala equivalent”NonEmptyList mirrors cats.data.NonEmptyList from Cats.
The key insight shared by both: lifting the non-empty constraint into the type eliminates a whole class of runtime checks. Functions that need at least one element can declare NonEmptyList in their signature rather than IList with a runtime guard.
// Scala / Catsimport cats.data.NonEmptyList
val nel = NonEmptyList.of(1, 2, 3)nel.head // 1 // totalnel.reduce(_ + _) // 6 // totalNonEmptyList.fromList(List.empty[Int]) // NoneNonEmptyList.fromList(List(1, 2)) // Some(NonEmptyList(1, 2))