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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With