A short way to update a nested value inside a record in Elm (0.18)

I am looking for a short way to update a nested value inside an entry in Elm (0.18).

In 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 example, 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 of these options. The shortest valid parameter that I see is the following:

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

This seems to be too much code to update a nested value. Do you know if there is a better / shorter way to achieve this?

+8
record elm
source share
3 answers

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

As they say, in functional languages โ€‹โ€‹you often find the concept of lenses useful for updating nested records. In arturopala / elm-monocle there is an Elm package that provides the ability to create and execute lenses to more accurately obtain and set the values โ€‹โ€‹of nested records.

Using this package, you can create lenses that allow you to do such things:

 personWithUpdatedCity = personCity.set "Madrid" person getCityOfPerson = personCity.get person 

The disadvantage is that you must write all the lens wiring code yourself. In Haskell, this wiring can be done by the compiler. At Elm, we donโ€™t have that luxury.

The Elm code required for the above lenses will be as follows:

 addressCityLens : Lens Address String addressCityLens = Lens .city (\cn a -> { a | city = cn }) personAddressLens : Lens Person Address personAddressLens = Lens .address (\ap -> { p | address = a }) personCity : Lens Person String personCity = compose personAddressLens addressCityLens 

As you can see, this is tedious and a lot more code than you can expect to set a nested value. Because of this boredom, you can stick with the let / in example, unless your code uses nested sets everywhere.

There is an older discussion on the topic , which makes it easier to set the value in Elm, but it has been inactive for some time.

+8
source share

If I need to do a lot of such an update, I create a helper

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

and use it for example

 updateAddress (\a -> { a | city = Madrid }) model 
+5
source share

You can create a function that receives Person (which is a type alias for { name: String, address: { city: String, country: String } ) and String and returns an updated record, for example:

 updateCityAdress : Person -> String -> Person updateCityAddress person newCity = { name: person.name, address = { country: person.address.country, city = newCity } > updateCityAddress person "Madrid" > { name = "Steven", address = { country = "Spain", city = "Madrid" } } 
0
source share

All Articles