Enumerating Union Types in Elm
aka Sum Types, now officially Custom Types
There are times when you want to enumerate a union type. A common example is to render the <option> elements within a <select>. In general each disjoint component of the union may have varying data associated with it.
Custom Types is the official Elm naming and is commonly/formerly known as a (disjoint) Union or Sum types. I'll keep using the Union types naming as it makes sense to me.
Let's start with a simple Union type used effectively like an enum of other languages:
type CarMake = AlfaRomeo | BMW | Ferarri | Ford | Honda | McLaren | Mercedes | Renault
Commonly a solution to having this accessible for enumeration is to manually define a List:
carMakes = [ AlfaRomeo, BMW, Ferarri, Ford, Honda, McLaren, Mercedes, Renault ]
The problem of course with this is that we have two parallel definitions that must be manually kept in sync which is exactly the sort of thing computers, compilers and type systems specifically should solve.
Note that we did add some information rather than merely performing double entry. The
carMakeslist defines an ordering whereas the
CarMaketype does not. The Elm language and compiler could infer this from the type definition and provide an enumeration mechanism but that's not part of the language.
So let's get on with it and see what's possible. Elm does ensure exhaustive case matching of Union types.
noop : CarMake -> CarMake
noop x =
case x of
AlfaRomeo -> x
BMW -> x
Renault -> x
Now if in the above code one of the component types was missed, the compiler would issue an error. This by itself doesn't solve our need. The main problem is that each case is processed independently and we don't have a way of processing every path to produce the full set of values. Even if we fed it our manual list of
carMakesit would only be able to produce the same set we put in replicating any accidental omission.
What we can do is what an accountant would do in such a situation, double-entry and cross check. We can create a
successor : Maybe CarMake -> Maybe CarMake
successor m =
case m of
Nothing -> AlfaRomeo
Some c ->
case c of
AlfaRomeo -> BMW
BMW -> Ferarri
Mercedes -> Renault
Renault -> Nothing
To produce a list from the
successordefinition we can start with
Nothingand produce each
CarMakeuntil we end with
carMakes2 = ... [Work in progress]
All that's left now is to assert
carMakesis the same as
We can do one better by writing this check once rather than for each Union type.
checkUnionSuccessor : List X -> Maybe X -> Maybe X -> Bool
checkUnionSuccessor xs successor =
... [Work in progress]