Can I compose a view with a number of Maybe components in a more efficient way?

I am creating a small view component that is composed of a number of optional Maybe String s (as arguments to Html.text ) interspersed with other Html Msg s.

My current solution is a bit unwieldy and I’m sure it could be handled more idiomatically. I’m using a number of nested case statements to decided what to show. I think a combination of Maybe.map s and Maybe.andThen s could work here but I can’t quite figure it out.

My current solution:

type alias Model =
    { subcomponentOne : Maybe SubcomponentOne
    , subcomponentTwo : Maybe SubcomponentTwo
    , modelString : Maybe String
    }

type alias SubcomponentOne =
    { subcomponentOneString : String
    }

type alias SubcomponentTwo =
    { subcomponentTwoString : String
    }

componentView : Model -> Html Msg
componentView model =
    case model.subcomponentOne of
        Nothing ->
            text ""

        Just subcomponentOne ->
            case model.modelString of
                Nothing ->
                    div [] [ Html.strong [] [ text subcomponentOne.subcomponentOneString ] ]

                Just modelString ->
                    case model.subcomponentTwo of
                        Nothing ->
                            div [] [ Html.strong [] [ text subcomponentOne.subcomponentOneString ], br [] [], text modelString ]

                        Just subcomponentTwo ->
                            div [] [ Html.strong [] [ text subcomponentOne.subcomponentOneString ], br [] [], text (modelString ++ " - " ++ subcomponentTwo.subcomponentTwoString) ]

Any pointers would be greatly appreciated!

you can pattern match against all 3:

componentView : Model -> Html Msg
componentView model =
    case ( model.subcomponentOne, model.modelString, model.subcomponentTwo ) of
        ( Nothing, _, _ ) ->
            text ""

        ( Just subcomponentOne, Nothing, _ ) ->
            div [] [ Html.strong [] [ text subcomponentOne.subcomponentOneString ] ]

        ( Just subcomponentOne, Just modelString, Nothing ) ->
            div [] [ Html.strong [] [ text subcomponentOne.subcomponentOneString ], br [] [], text modelString ]

        ( Just subcomponentOne, Just modelString, Just subcomponentTwo ) ->
            div [] [ Html.strong [] [ text subcomponentOne.subcomponentOneString ], br [] [], text (modelString ++ " - " ++ subcomponentTwo.subcomponentTwoString) ]

I tried a Maybe.map version but it looks worse.

componentView : Model -> Html Msg
componentView model =
    let
        viewModelString modelString =
            [ br [] []
            , case model.subcomponentTwo of
                Nothing ->
                    text modelString

                Just subcomponentTwo ->
                    text (modelString ++ " - " ++ subcomponentTwo.subcomponentTwoString)
            ]

        viewSubOne subcomponentOne =
            Html.strong [] [ text subcomponentOne.subcomponentOneString ]
                :: Maybe.map viewModelString model
                |> Maybe.withDefault []
                |> List.concat
                |> div []
    in
    Maybe.map viewSubOne model.subcomponentOne
        |> Maybe.withDefault (text "")
3 Likes

You can also have a custom type instead of a record for your model

type Model 
	= One { subcomponentOneString : String }
    | Two { subcomponentOneString : String, modelString : String }
	| Three { subcomponentOneString : String, subcomponentTwoString, modelString : String }

and use case…of in the view with that.

case model of
	One _ ->
	Two _ ->
	Three _ ->
7 Likes

Wow! I didn’t realise you could pattern match against multiple values simultaneously. Very cool, thanks.