"You can't do nested record updates in Elm."
Tags: elm, webdev, beginners
Published: 2022-01-28 05:08:39

At least, not in an intuitive way. It's one of the first pain-points that a new developer runs into when getting started with the language.

Given:

```
type alias Model =
    { person : Person
    }

type alias Person =
    { firstName : String
    , lastName : String
    }
```

... the natural thing to try when writing an `update` function looks like this:

```
update msg model =
    case msg of
        UpdatedFirstName firstName ->
            { model | person =
              { model.person |
                  firstName = firstName
              }
            }
```

The compiler won't like it, and neither will you; this is not The Way.

The natural next step always looks something like this:

```
update msg model =
    case msg of
        UpdatedFirstName firstName ->
            let
                currentPerson = model.person

                updatedPerson =
                    { currentPerson | firstName = firstName }
            in
            { model | person = updatedPerson }
```

But we're better. We can do better. Follow me.

----

Elm is a really, _really_ simple language. Everything in the language follows a set of internally-consistent semantics; I find that when something does _not_ work as I'd expect it to, it means that my expectations are wrong, which means that my mental model of the language is incomplete or wrong. (NB: people that claim to design systems that "work just like you'd expect them to" - how can they make that claim? Who is this subset of people for whom these things work exactly as they'd expect them to; and are they even the right subset of people to optimize for?[^1])

A few weeks ago, one of those Elm Lightbulbs fell neatly into its socket and I noticed one of Elm's guiding principles:

## Functions go on the left. Parameters go on the right.

(Yes, I'm aware of `|>`; do you know how a mirror works?)

This seems obvious enough, but it has interesting implications, not all of which are immediately apparent. Everybody knows that if you have an `a` and you want to turn it into a `b`, all you have to do is put a function to the left of `a` that turns it into `b` - so you write a function, and call it:

```
aToB : a -> b

aToB valueA
```

But what if you only need to use it once? Why not just write a lambda?

```
(\a -> b) valueA
```

----

But you already knew that, and I still haven't addressed the elephant in the room. You probably knew, too, that you can write a lambda that takes a value and returns a record.

So why haven't I seen this before?

```
update msg model =
    case msg of
        UpdatedFirstName firstName ->
            { model |
                person =
                    (\p ->
                        { p | firstName = firstName }
                    ) model.person
            }
```

## "Wow, John. Neat party trick. But -"

You're right. This isn't a silver bullet. Sometimes, you should just write a function that does this for you. Some people use a builder pattern[^2], so they can do neat stuff like this:

```
update msg model =
    case msg of
        UpdatedFirstName firstName ->
            { model | person =
                model.person
                    |> withUpdatedFirstName firstName
            }

withUpdatedFirstName : String -> Person -> Person
withUpdatedFirstName firstName =
    (\person -> { person | firstName = firstName } )
```

But you don't always need to reach for a builder pattern, when all you need to do is update a nested record once in your `update` function.

Besides, that's really not even what this little essay was about, in the first place:

**As a beginner or intermediate Elm developer, if you get the sense that "there must be a better way to do this" - there probably is. Go look for it, and don't stop looking until you find it.**

That's all. Good night, take care.

----

Update / edit: I posted this on the Elm Slack[^3] last night, and Jeroen Engels was kind enough to read though this, detail some errata, and suggest some improvements. I'm just going to quote him, here:

> I think one of the options I end up going for in the end, is to have a separate function to update Person, in a separate Person module where the Person type is opaque. That's I think usually where you get the most benefits and it feels least cumbersome (if you already have the opaque type), and I think it would be valuable to mention it here.

Opaque types are beyond the scope of this post[^4], but [Charlie Koster does a great job of explaining them, here, for anyone interested.](https://ckoster22.medium.com/advanced-types-in-elm-opaque-types-ec5ec3b84ed2)

[^1]: Love or hate DHH, this document is still relevant: https://rubyonrails.org/doctrine - `Ctrl+F "surprise"` to get to the relevant section.
[^2]: https://sporto.github.io/elm-patterns/basic/builder-pattern.html
[^3]: http://elmlang.herokuapp.com/
[^4]: Because I don't feel qualified to explain them, and because my schtick is "saving beginners from themselves" and "atoning for my sins"; I still have much to learn.