Background
Many functions in Elm that work with List
s work equally well with empty and non-empty lists, for example:
-
List.sum
returns 0 for an empty list -
List.map
returns an empty list if given an empty list -
Html.div
does what you’d expect if given empty lists for attributes and/or children
However, other functions only really make sense for non-empty lists, and have a couple main ways of expressing/dealing with that:
- Return a
Maybe
:List.maximum
has the signatureList comparable -> Maybe comparable
, returningNothing
for an empty list - Require an explicit ‘first item’ argument:
Random.uniform
has the signaturea -> List a -> Generator a
, forcing you to call it asRandom.uniform x [ y, z ]
instead ofRandom.uniform [ x, y, z ]
Either one of these could have been written the other way:
-
List.maximum
could have had the signaturecomparable -> List comparable -> comparable
-
Random.uniform
could have had the signatureList a -> Maybe (Generator a)
Approach (1) is a bit simpler at first glance, but forces you to deal with a Maybe
even if you can guarantee the list is non-empty. Approach (2) looks a bit weird, and doesn’t handle the empty list case at all, but lets you avoid the Maybe
. (There’s also the third approach of using a dedicated non-empty list type like mgold/elm-nonempty-list
, but I wanted to see how much could be done without requiring a separate type.)
Proposal
It occurred to me that we might be able to get the best of both worlds by adding (perhaps in the list-extra
package) a function
ifNonEmpty : (a -> List a -> b) -> List a -> Maybe b
ifNonEmpty function list =
case list of
first :: rest ->
Just (function first rest)
[] ->
Nothing
and then following a convention of using approach (2) instead of (1) anywhere that we write a function that requires a non-empty list. We could then write code like
import List.Extra as List
List.maximum 2 [ 1, 3 ]
--> 3
-- Read as "if the list is non-empty, returns its maximum"
List.ifNonEmpty List.maximum [ 2, 1, 3 ]
--> Just 3
List.ifNonEmpty List.maximum []
--> Nothing
[ 2, 1, 3 ]
|> List.filter (\n -> n < 3)
|> List.ifNonEmpty List.maximum
--> Just 2
[ 2, 1, 3 ]
|> List.filter (\n -> n > 3)
|> List.ifNonEmpty List.maximum
|> Maybe.withDefault -1
--> -1
which to me seems like a pretty nice way to handle both possibly-empty and guaranteed-non-empty lists of values.
Thoughts?
What do you think? Are there places where this breaks down or becomes awkward? If people like the idea, then I’d be happy to make a PR to list-extra
, and start updating my own packages to use form (2). (This whole train of thought was kicked off when trying to come up with a nice solution to this elm-geometry
issue).