Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concise way of updating a nested value inside a record in Elm (0.18)

Tags:

record

elm

I am looking for a concise way of updating a nested value inside a record in Elm (0.18).

Given the following example:

person = { name = "Steven", address = { country = "Spain", city = "Barcelona" } }

I can update person.name to "Steve" using the following expression:

{ person | name = "Steve" }

However, I am looking for a way to update a nested value. For instance, I would like to update person.address.city to "Madrid". I tried the following:

{ person | address.city = "Madrid" } 
{ person | address = { address | city = "Madrid" } } 
{ person | address = { person.address | city = "Madrid" } } 

The compiler rejects all these variations. The shortest valid option I see is:

let personAddress = person.address in { person | address = { personAddress | city = "Madrid" } }

This seems to be a bit too much code just to update a nested value, Do you know if there is a better/shorter way of achieving that?

like image 808
jakubr Avatar asked Feb 08 '17 16:02

jakubr


2 Answers

Your last example with the let/in syntax is as concise as is possible in Elm 0.18 without resorting to additional packages.

That being said, in functional languages, you will often find the concept of Lenses useful for updating nested records. There is an Elm package at arturopala/elm-monocle which provide the ability to construct and execute lenses for more concisely getting and setting nested record values.

Using that package, you could build up lenses that would let you do concise things like this:

personWithUpdatedCity = personCity.set "Madrid" person

getCityOfPerson = personCity.get person

The downside is that you have to write all the lens wiring code yourself. In Haskell, this wiring can be done by the compiler. In Elm, we don't have that luxury.

The Elm code needed for the above lenses would be this:

addressCityLens : Lens Address String
addressCityLens =
    Lens .city (\cn a -> { a | city = cn })

personAddressLens : Lens Person Address
personAddressLens =
    Lens .address (\a p -> { p | address = a })

personCity : Lens Person String
personCity =
    compose personAddressLens addressCityLens

As you can see, it's tedious and much more code than you may expect to set a nested value. Due to that tedium, you may want to stick to the let/in example for the time being, unless your code uses nested sets all over the place.

There is an older discussion on the topic of making setting value easier in Elm here, but it hasn't been active for some time.

like image 92
Chad Gilbert Avatar answered Nov 10 '22 15:11

Chad Gilbert


If I need to do this sort of update a lot, I build a helper

updateAddress : ( Address -> Address ) -> Model -> Model 
updateAddress fn m = 
  {m | address = fn m.address }

and use it, e.g.

updateAddress (\a -> { a | city = Madrid }) model
like image 9
Simon H Avatar answered Nov 10 '22 16:11

Simon H