138

Let's say I have the following record ADT:

data Foo = Bar { a :: Integer, b :: String, c :: String }

I want a function that takes a record and returns a record (of the same type) where all but one of the fields have identical values to the one passed as argument, like so:

walkDuck x = Bar { a = a x, b = b x, c = lemonadeStand (a x) (b x) }

The above works, but for a record with more fields (say 10), creating a such function would entail a lot of typing that I feel is quite unnecessary.

Are there any less tedious ways of doing the same?

Chris Stryczynski
  • 25,295
  • 38
  • 135
  • 245
  • 4
    Record syntax for updating exists, but quickly gets cumbersome. Take a look at [lenses](http://stackoverflow.com/questions/5767129/lenses-fclabels-data-accessor-which-library-for-structure-access-and-mutatio) instead. – Cat Plus Plus Feb 19 '13 at 13:15

3 Answers3

183

Yes, there's a nice way of updating record fields. In GHCi you can do --

> data Foo = Foo { a :: Int, b :: Int, c :: String }  -- define a Foo
> let foo = Foo { a = 1, b = 2, c = "Hello" }         -- create a Foo
> let updateFoo x = x { c = "Goodbye" }               -- function to update Foos
> updateFoo foo                                       -- update the Foo
Foo {a = 1, b = 2, c = "Goodbye" }
Chris Taylor
  • 45,772
  • 13
  • 106
  • 152
  • 12
    The `RecordWildCards` extension can be nice as well, to “unpack” fields in a scope. For updates it’s not quite as nice though: `incrementA x@Foo{..} = x { a = succ a }` – Jon Purdy Feb 19 '13 at 14:14
  • 2
    BTW, in Frege (a Haskell for the JVM) you would define the function as ```updateFoo x = x.{ c = "Goodbye" }``` (note the ```.``` operator). – 0dB Feb 06 '16 at 14:34
  • Nice video by the way https://www.youtube.com/watch?v=YScIPA8RbVE – Damian Lattenero Nov 11 '19 at 23:23
  • Thanks. Sadly been a long time since I wrote any Haskell! – Chris Taylor Nov 12 '19 at 09:25
40

This is a good job for lenses:

data Foo = Foo { a :: Int, b :: Int , c :: String }

test = Foo 1 2 "Hello"

Then:

setL c "Goodbye" test

would update field 'c' of 'test' to your string.

Don Stewart
  • 136,266
  • 35
  • 360
  • 464
  • 5
    And lenses-like packages often define operators in addition to functions for getting and setting fields. For example, `test $ c .~ "Goodbye"` is how `lens` would do it iirc. I'm not saying this is intutitive, but once you know the operators then I expect it would come as easily as `$`. – Thomas M. DuBuisson Feb 19 '13 at 15:06
  • 3
    Do you know where _setL_ has gone? I'm importing _Control.Lens_, but ghc is reporting that _setL_ is undefined. – dbanas Aug 14 '17 at 13:12
  • 1
    use set instead of setL – Subhod I Mar 08 '19 at 08:12
  • That is awful procedual thinking. in Haskell, don’t say what it does, but what it *is*. That’s kinda the point of functional programming. Writing `test { c = "Goodbye" }` is a much more natural way of saying “`test`, but with `c` being `"Goodbye"`”. Whoever designed that `setL` function, missed the whole point of functional programming. … If record syntax has flaws, which it does, record syntax itself needs a fix. Not something that effectively turns Haskell into a C-like again. – Evi1M4chine Feb 23 '22 at 00:06
25

You don’t need to define auxiliary functions or employ lenses. Standard Haskell has already what you need. Let’s take the example by Don Stewart:

data Foo = Foo { a :: Int, b :: Int , c :: String }

test = Foo 1 2 "Hello"

Then you can just say test { c = "Goodbye" } to get an updated record.

Wolfgang Jeltsch
  • 731
  • 5
  • 10