Search code examples
elmalgebraic-data-types

Pattern-matching the nested union types


I'm modeling a count up/down timer. After the origin date is set, the timer displays how much time has passed since-, or remains till that origin date.

type OriginDefined
    = Up Date
    | Down Date


type Origin
    = OriginDefined
    | OriginUndefined


type Model
    = Tick OriginDefined
    | Edit Origin

Thus, the timer is ticking only when the origin date is defined. When the origin is edited though, it may or may not be previously defined.

Now I need a function to return the defaultValue for the date input, when we're in the Edit mode.

dateInputDefaultValue : Origin -> String
dateInputDefaultValue origin =
    case origin of
        OriginUndefined ->
            ""

        OriginDefined ->
            ...

Here I struggle to destructure the origin further as an Up date or Down date. In the 2nd branch of the case expression the compiler refuses to treat the origin as anything more specific than just an Origin.

Here's an Ellie https://ellie-app.com/3zKCcX87wa1/0

How should I deal with a model like that? Should I model in a different way?


Solution

  • Your types compile, but you have two different definitions of OriginDefined: One is a type called OriginDefined which has two constructors, Up and Down. The other is a parameterless constructor on the type Origin.

    My hunch is that you were trying to get the OriginDefined constructor on Origin to carry a value of type OriginDefined. In order to do that, you would have to define a parameter of type OriginDefined on the OriginDefined constructor:

    type Origin
        = OriginDefined OriginDefined
        | OriginUndefined
    

    Now you have a type that is isomorphic to Elm's Maybe type, so perhaps it would be less confusing and more idiomatic to remove the OriginDefined type and replace the definition of Origin with this:

    type Origin
        = Up Date
        | Down Date
    

    Now you can use Maybe in the places you were previously using defined/undefined nomenclature:

    type Model
        = Tick (Maybe Origin)
        | Edit Origin
    

    Pattern matching on Maybe Origin could look like this:

    dateInputDefaultValue : Maybe Origin -> String
    dateInputDefaultValue origin =
        case origin of
            Nothing ->
                ""
    
            Just (Up date) -> 
                "…"
    
            Just (Down date) -> 
                "…"