Skip to content
Pyrula

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() # 3

Pass at least one argument. The first positional argument is head; the rest are the tail.

NonEmptyList(42) # single-element
NonEmptyList(1, 2, 3) # three elements
NonEmptyList.of(1, 2, 3) # same as the constructor

Zero arguments raise TypeError at construction time.

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))

Because the list is guaranteed non-empty, these operations always succeed:

OperationReturnsNotes
head()TFirst element
last()TLast element
reduce(f)TLeft-fold seeded by head, no initial value needed
nel = NonEmptyList(1, 2, 3, 4)
nel.head() # 1
nel.last() # 4
nel.reduce(lambda a, b: a + b) # 10

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) # 60

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)
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) # 3

Two NonEmptyList values are equal when they have the same length and every element is equal.

NonEmptyList(1, 2) == NonEmptyList(1, 2) # True
NonEmptyList(1, 2) == NonEmptyList(1, 3) # False

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 / Cats
import cats.data.NonEmptyList
val nel = NonEmptyList.of(1, 2, 3)
nel.head // 1 // total
nel.reduce(_ + _) // 6 // total
NonEmptyList.fromList(List.empty[Int]) // None
NonEmptyList.fromList(List(1, 2)) // Some(NonEmptyList(1, 2))