Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Haskell idiom for updating a nested data structure?

Let's say I have the following data model, for keeping track of the stats of baseball players, teams, and coaches:

data BBTeam = BBTeam { teamname :: String,                         manager :: Coach,                        players :: [BBPlayer] }        deriving (Show)  data Coach = Coach { coachname :: String,                       favcussword :: String,                      diet :: Diet }        deriving (Show)  data Diet = Diet { dietname :: String,                     steaks :: Integer,                     eggs :: Integer }        deriving (Show)  data BBPlayer = BBPlayer { playername :: String,                             hits :: Integer,                            era :: Double }        deriving (Show) 

Now let's say that managers, who are usually steak fanatics, want to eat even more steak -- so we need to be able to increase the steak content of a manager's diet. Here are two possible implementations for this function:

1) This uses lots of pattern matching and I have to get all of the argument ordering for all of the constructors right ... twice. It seems like it wouldn't scale very well or be very maintainable/readable.

addManagerSteak :: BBTeam -> BBTeam addManagerSteak (BBTeam tname (Coach cname cuss (Diet dname oldsteaks oldeggs)) players) = BBTeam tname newcoach players   where     newcoach = Coach cname cuss (Diet dname (oldsteaks + 1) oldeggs) 

2) This uses all of the accessors provided by Haskell's record syntax, but it is also ugly and repetitive, and hard to maintain and read, I think.

addManStk :: BBTeam -> BBTeam addManStk team = newteam   where     newteam = BBTeam (teamname team) newmanager (players team)     newmanager = Coach (coachname oldcoach) (favcussword oldcoach) newdiet     oldcoach = manager team     newdiet = Diet (dietname olddiet) (oldsteaks + 1) (eggs olddiet)     olddiet = diet oldcoach     oldsteaks = steaks olddiet 

My question is, is one of these better than the other, or more preferred within the Haskell community? Is there a better way to do this (to modify a value deep inside a data structure while keeping the context)? I'm not worried about efficiency, just code elegance/generality/maintainability.

I noticed there is something for this problem (or a similar problem?) in Clojure: update-in -- so I think that I'm trying to understand update-in in the context of functional programming and Haskell and static typing.

like image 361
Matt Fenwick Avatar asked Sep 09 '11 17:09

Matt Fenwick


1 Answers

Record update syntax comes standard with the compiler:

addManStk team = team {     manager = (manager team) {         diet = (diet (manager team)) {              steaks = steaks (diet (manager team)) + 1              }         }     } 

Terrible! But there's a better way. There are several packages on Hackage that implement functional references and lenses, which is definitely what you want to do. For example, with the fclabels package, you would put underscores in front of all your record names, then write

$(mkLabels ['BBTeam, 'Coach, 'Diet, 'BBPlayer]) addManStk = modify (+1) (steaks . diet . manager) 

Edited in 2017 to add: these days there is broad consensus on the lens package being a particularly good implementation technique. While it is a very big package, there is also very good documentation and introductory material available in various places around the web.

like image 159
Daniel Wagner Avatar answered Oct 10 '22 22:10

Daniel Wagner