Keys to Elm: Updating nested records

Updating nested records is not the same as in the Javascript world. This article guides you through the Elm approach.

azurewaters
3 min readJan 22, 2023
Photo by Giorgio Trovato on Unsplash

Note: The “Keys to Elm” series describes a few basics of Elm that aren’t covered in the documentation or on the Internet much, just so you don’t spend time discovering them on your own.

“Records” in Elm are similar to objects in Javascript. (The main difference is that objects can have associated methods but records cannot — they just hold data.) In Javascript, we can do “nested updates” — things on the lines of customer.billingAddress.zip = ‘12345’. You, naturally, try this in Elm only to figure two things:

  1. Elm doesn’t use a dot syntax to update fields in records.
  2. You can’t directly update fields of nested records in Elm.

Different syntax

What is Elm’s syntax to update records? It’s this:

newRecord = { oldRecord | field = newValue }

If you are looking at this syntax and asking yourself “Why?”, one answer is that you can do this:


newRecord = { oldRecord | field1 = newValue1, field2 = newValue2 }

In other words, you can update multiple fields with just one line.

Nested updates

We now know how to update fields of records. But, some fields can be records themselves, like billingAddress in this example:

-- NESTED STRUCTURE

type alias Customer =
{ name : String
, billingAddress : Address -- The "billingAddress" is of the "Address" type, which is shown below.
, mobile : String
}

type alias Address =
{ street : String
, city : String
, zip : String
}

How does one update fields of nested records, like the zip of a customer’s billingAddress? Elm doesn’t let you do the following, for instance:

-- This won't work 
newCustomer =
{ oldCustomer | billingAddress = { oldCustomer.billingAddress | zip = newValue } }

So, how does one do this? Firstly, the Elm community wants you to ask yourself whether you really need that nested structure. If there’s a legitimate, technical reason to nest records, then the way to update a nested field is to break the steps down. Like so:

Note: The above code can be more concise but I’ve shown it as is for clarity.

What’s happening here?

  1. Since we can’t update nested fields, you have to extract the customer’s billingAddress into its own variable. That’s what’s happening in line 4.
  2. You can then update its zip as shown on line 8.
  3. Next, you update the customer’s record with the updated billing address, as in line 10.

Alternatives

If the reason for using the nested data structure is organisation and not a technical one (like that you use Address independently and often in your code), here are a couple of alternatives:

One

Flatten the data structures but use part of the fields’ name to group them. Compare the first approach (“Nested Structure”) to the second one (“Flat Structure”), below:

Two

Use comments in your data structure to section-off fields.

type alias Model =
{ newName : String
, newZip : String

-- Customer's details
, name : String
, mobile : String

-- Billing address
, billingAddressStreet : String
, billingAddressCity : String
, billingAddressZip : String
}

If the organisation is for you and not for the compiler, why not use the above approach? I’m not saying there’s never a need to use nested records but keep in mind how many more steps you need to take to update nested fields; compare the example in the Nested Updates section above, to this:

{ customer | billingAddressZip = newZip }

Point made?

Conclusion

Nested updates can be done in Elm. But, they’re cumbersome, almost by design. It won’t do to simply say that that’s what you used to do in Javascript. So, think hard before nesting records.

That’s it for this short article. Have fun with Elm, people! Cheers!

If you’d like, you can support my caffeine habit with a virtual coffee!

--

--

No responses yet