When modeling data in a typed language like Elm or Haskell, some
combinations of types are a bit strange. Take
Maybe (List a). It has two
Just . Does your code distinguish between these
two? Or do they mean the same thing?
Lists already have an empty state:
. If you only need to model “list of
values” and “no values” then you can rely on
In the following code, we see that
Just  are semantically the
same: they represent the absence of results.
view : Maybe (List Int) -> Html a view calculation = case calculation of Nothing -> text "No Results" Just  -> text "No Results" Just numbers -> viewResults numbers
Maybe doesn’t add any extra information so we can simplify our
case statement by dropping the
view : List Int -> Html a view calculation = case calculation of  -> text "No Results" _ -> viewResults calculation
Just  are being used to represent different
view : Maybe (List Int) -> Html a view calculation = case calculation of Nothing -> text "In Progress" Just  -> text "No Results" Just numbers -> viewResults numbers
A custom union type will do a better job at communicating the semantic differences. Now you can see at a glance what possible states a calculation can have.
type Calculation = InProgress | NoResults | Results (List Int)
Observant readers might have noticed that it’s still possible to end up with the
Results  using the union type defined above. Since we’ve
separated out the empty state from the list, we might want to get fancy and
guarantee a non-empty list for
type Calculation = InProgress | NoResults | Results (List.NonEmpty Int)
Legitimate uses of
Maybe (List a)
Does this mean that we should avoid
Maybe (List a) at all cost? No. Consider
getting the head of a doubly-nested list:
[[1,2], [3,4]] |> List.head -- returns Just [1,2]
Maybe (List a) is a perfectly valid return type here. It represents a list
that may or may not be present. It does not carry any extra implicit semantic
Returning an optional list often makes sense when querying another data
structure. Not only does it correctly model the uncertainty in the querying, it
also allows functions to be chained nicely with other
firstChild : Int -> MyTree a -> Maybe (MyTree a) firstChild id myTree = myTree |> MyTree.childrenOfNode 10 |> Maybe.andThen List.head
Maybe (List a) is a type that introduces ambiguity when data modeling.
Depending on what you’re trying to represent, a simple
List a or a custom union
type may better model your system. Don’t discount it entirely though. It can
often make sense as the return type of a query function.