Enumerating Union Types in Elm

aka Sum Types, now officially Custom Types

Problem statement

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 carMakes list defines an ordering whereas the CarMake type 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 carMakes it 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 definition:

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 successor definition we can start with Nothing and produce each CarMake until we end with Nothing.

carMakes2 = ... [Work in progress]

All that's left now is to assert carMakes is the same as carMakes2 .

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]

Last updated