Updating nested records

Hi,

I am experimenting with records and I am a little bit confused. In the example person2 and person3 should be the same, but person3 gives me an error…
Can anybody explain the difference?

type alias Date =
    { day : Int
    , month : Int
    , year : Int
    }

type alias Person =
    { firstName : String
    , lastName : String
    , birthDate : Date
    }

person1 : Person
person1 =
    { firstName = "Tom"
    , lastName = "Smith"
    , birthDate = { day = 6, month = 6, year = 2006 }
    }

person2 : Person
person2 =
    let date = person1.birthDate
    in
    { person1 | birthDate = { date | month = person1.birthDate.month + 1 } }

person3 : Person
person3 =
    { person1 | birthDate = { person1.birthDate | month = person1.birthDate.month + 1 } }

The Elm compiler only accepts a name in the update position, not an expression. The issue is tracked here.

There’s no fundamental reason why this is the case, semantically it would make no difference, but that’s the way it is. I believe the Gren fork has fixed this.

2 Likes

I believe the reason that Evan has given is this:

Also in the docs you find

People coming from JavaScript tend to (1) overuse records and (2) have a habit of breaking records into smaller records and to distribute them among a bunch of different files. These are both traps in Elm! (source)

so person2 would be the idomatc way to write it in elm.

Also, if you have have to update a field a lot, it might be helpful to write a utility function for it:

mapMonth : (Int -> Int) -> Date -> Date
mapMonth fun date =
   { date | month = fun date.month }

person4 : Person
person4 =
   { person1 | birthDate = mapMonth (\n -> n + 1) person1.birthDate } 
1 Like

OK, thank you. I wasn’t sure if I was doing something wrong or not understanding correctly. But then it’s just a design decision by Evan. I would find it logical to use the notation of person2. But well, that’s how it is.

I’m curious to see how Gren and Elm will develop…

1 Like

Another way to write it is as follows:

updateBirthDate : (Date -> Date) -> Person -> Person
updateBirthDate transform person =
    { person | birthDate = transform person.birthDate }

person5 : Person
person5 =
    updateBirthDate (\date -> { date | month = date.month + 1 }) person1

This way you can update any field of birthDate and not just month.

For another example, see updateForm from Page.Login in rtfeldman/elm-spa-example.

This is the only error message in Elm that I find to be genuinely unhelpful. Most others explain what the problem is and explain how to fix it, but this one is really cryptic especially since it seems like the mistake should be valid code.

1 Like

You could also combine both approaches above from @dwayne and @Lucas_Payr:

updateBirthMonth : Int -> Person -> Person
updateBirthMonth newMonth =
    updateBirthDate <|
        mapMonth <|
            \_ -> newMonth


incrementBirthMonthBy : Int -> Person -> Person
incrementBirthMonthBy increment =
    updateBirthDate <|
        mapMonth <|
            \n -> n + increment

person6 : Person
person6 =
    updateBirthMonth 7 person1


person7 : Person
person7 =
    incrementBirthMonthBy 2 person1

@dwayne has a small typo

updateBirthDate (\date -> { date | month = month + 1 }) person1

should be

updateBirthDate (\date -> { date | month = date.month + 1 }) person1

@paulh Thanks, fixed!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.