Non-empty list vs (head, tail)

I saw some chatter in slack the other day about two ways of representing a list that is guaranteed to never be empty. One way is to use a module that encapsulates this invariant, such as non-empty-list. Another way is to use a tuple (a, List a) where the first element is the head and the second element in the tuple is the remainder of this list (which could be an empty list)

It just so happens I have a package published where I was using non-empty-list and I changed the underlying implementation to now use a tuple…

Non-empty vs tuple diff

Pros:

  • One less dependency and import statement
  • Once the list is reconstructed we can use elm-lang/core’s List functionality
  • Removed custom non-empty helper function

Cons:

  • Reconstructing the list from head / tail is a bit annoying
  • Argument deconstruction + “as” syntax is less readable to me (lines 148 and 198 in Genetic.elm)
  • I speculate that (a, List a) isn’t beginner friendly

I’m conflicted on the tradeoffs here. Depending on the context and team any of those three cons can be further emphasized.

I’m curious if someone can demonstrate in code or has past experiences with larger codebases / teams to help determine if the balance tips one way or the other.

Where and why would a beginner need to see this? Is there a problem with using a type alias instead?

type alias NonemptyList a = (a, List a)

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.