loading...
ElmUpdate

Playing with "with" in Elm

rwoodnz profile image Richard Wood ・2 min read

Inspired by With* Functions in Elm by Charlie Koster I determined to bring in a set of 'with' functions into a recent work project.

Koster's example allows you to create a default button and then pipe to one or more modifiers rather than define the full record of modifiers each time.

From his code:

default "Small Call to Action" Msg
    |> makeSolid
    |> makeDisabled
    |> view

My code was in a worse state. For my calling functions I had unnamed parameters rather than an options record as a single parameter. These had grown as the application grew, to a point where it was wasn't always obvious what a parameter might be for.

singleCheckBox 
    editingUser.blocked 
    (ToUsers << ChangeBlock) 
    "login blocked" 
    False

Rather than separate out required parameters and retain them as regular parameters I decided for simplicity to put everything in the option parameter, to benefit from them all being named.

Then when I started to create the "with" statements I soon realised the number of "with" modifiers would blow out, yet a lot of what is needed is the same concept between input types.

So I decided to use the record modifying syntax that allows us to be generic. Now I can use the one "with" modifier for title, for example, across the different buttons and inputs that I have.

withTitle : String -> { a | title : String } -> { a | title : String }
withTitle value record =
    { record | title = value }

You may realise I do not have as much type safety going on as Koster did. His is a module just for a button and tied down to it. I have a module for a collection of different inputs. I'm not clear yet if I can retain the generic approach if I lock it down further.

I also read this exchange started by Thomas Kagan where the pros and cons of pipelines vs list of attributes are discussed.

I decided the list of attributes approach was more consistent with rest of my code. I also placed the options type definitions and default values in the context of each.

type alias InputOptions =
    { textContent : String
    , changeMsg : String -> Msg
    , placeholderText : String
    , title : String
    , titleNote : String
    , disable : Bool
    }

inputBox : List (InputOptions -> InputOptions) -> Element Msg
inputBox optionModifiers =
    let
        defaults =
            { textContent = ""
            , changeMsg = \_ -> Noop
            , placeholderText = ""
            , title = ""
            , titleNote = ""
            , disable = False
            }

        options =
            List.foldl
                identity
                defaults
                optionModifiers
    in
    ...

I applied this approach to all of input views that I have - both inputs as well as views that present collections of inputs. I've ended up with a list of 20 "with" modififiers I can use generically.

Now my calls to inputs look great

standardButton
    [ withButtonMsg (ToUsers (ConfirmDeleteUser userId))
    , withTitle "Delete Permanently"
    , withColor color.red
    ]

I can tick off that the code is more readable excepting the addition of the foldl statements. And above all now only the parameters beyond default values are passed and they are all named.

Posted on by:

rwoodnz profile

Richard Wood

@rwoodnz

Remote developer working in Elm and JS for clients globally.

ElmUpdate

Elm news and experiences

Discussion

pic
Editor guide