understanding union types in elm

Watching Making Impossible States Impossible and reading Higher Level Coding with Elm Union Types were enlightening in my Elm development. Now, I look for places were I can refine my data model by using union types. Union types allow me to replace conditionals with pattern matching case statements. This pattern is much cleaner and easier to understand. The following is an example of how I used union types to refactor a hamburger.

Elm Model

-- BEFORE

type Msg
    = DisplayHamburgerItems

type alias Model = 
    { hamburger_open : Bool
    }

-- AFTER

type Msg
    = DisplayHamburgerItems Hamburger

type Hamburger
    = Open
    | Closed


type alias Model = 
    { hamburger : Hamburger
    }

Elm Update

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =

    -- BEFORE

    case msg of
        DisplayHamburgerItems ->
            if model.hamburger_open == True then
                let
                    items = List.append model.display_hamburger ["About", "Contact", "Menu"]
                in
                    ( { model | display_hamburger = items, hamburger_open = False }, Cmd.none )
            else
                ( { model | display_hamburger = [], hamburger_open = True }, Cmd.none 


    -- AFTER

        DisplayHamburgerItems msg ->
            case msg of
                Open ->
                    let
                        items = List.append model.display_hamburger ["About", "Contact", "Menu"]
                    in
                        ( { model | hamburger = Open, display_hamburger = items }, Cmd.none )
                Closed ->
                    ( { model | hamburger =  Closed, display_hamburger = [] }, Cmd.none )

Elm View

view : Model -> Html Msg
view model =

    -- BEFORE

    i [ onClick (DisplayHamburgerItems) ] []

    -- AFTER

    i [ onClick (DisplayHamburgerItems (toggleHamburger model.hamburger)) ] []

viewHamburgerItems : Model -> Html Msg
viewHamburgerItems model =

    -- BEFORE

    if model.hamburger_open == False then
        div [] (List.map item model.display_hamburger)
    else
        div [] []

    -- AFTER

    case model.hamburger of
        Open ->
            div [] (List.map item model.display_hamburger)
        Closed ->
            div [] []

It is easier to reason about Hamburger with Open and Closed types than checking against a Bool. Pattern matching on the union type is expressive and is really helpful when the union types grow in complexity.

Full code here.

comments powered by Disqus