Search code examples
elmpointfree

Equivalent setter to match the `.foo` getter for an Elm record type


I need to write a lot of typesafe getters and setters for my model (which is a record). The getters are super-concise and I am happy with that.

getFoo = .foo

This is perfect to use as an inline function without giving it a name:

Maybe.map .foo maybeModel

Only one extra character (.) over the bare minimum required code (the field name). Cannot get any more boilerplate-free.

But what is the shortest way to write the equivalent setter? Now I do

Maybe.map2 (\x v -> {x | foo = v} ) maybeModel maybeValue

That is now no longer "pointless" and there is a lot of boilerplate around the foo, most of it hard-to-type special characters.

For clarification, there is a related thread about how to get rid of having to write a separate getter for each field. I'm fine with that, but I just want it to be easier on fingers and eyes.


Solution

  • There is no equivalent short-hand of .foo syntax and it's something that comes up time and time again.

    There are long discussions on the topic going back years on the mailing list if you want to find out more around Evan's reasoning for omitting such a thing (e.g. proposals for making setter syntax like !foo or other special symbols). Like many of Elm's design decisions, I think the answer boils down to keeping a single way of doing something and keeping it easy for newcomers.


    The most concise way of working around the clumsy setter syntax that I've found is to create a single setter for each value of the form:

    setFoo : Foo -> Model -> Model
    setFoo foo model = { model | foo = foo }
    

    Since the model value comes last, it makes it easy to compose, either by pipelines:

    updateModel : Foo -> Bar -> Model -> Model
    updateModel foo bar model =
        model
            |> setFoo foo
            |> setBar bar
    

    Or if you want to be a little more concise, you can use >> to shorten the above:

    updateModel2 : Foo -> Bar -> Model -> Model
    updateModel2 foo bar = setFoo foo >> setBar bar
    

    Your Maybe.map2 example could be written like this, where maybeModel and maybeValue are swapped from your example:

    Maybe.map2 setFoo maybeValue maybeModel
    

    Sure, you have to create a lot of boilerplate setter code, but in my opinion it feels much more natural to work with and looks much better than the setter syntax the language provides.