Nah. Not if you use an appropriate form to extract the desired member while leaving the rest alone.
data Person = Person { name :: String ,
age :: Integer ,
height :: Integer ,
weight :: Integer
}
deriving (Show)
makeOlder p@(Person { age = current }) increase = p { age = current + increase }
main = let b = (Person { name = "Bob", age = 50, height = 72, weight = 190 })
c = b { age = (age b) + 10 }
d = makeOlder b 10
in do
putStrLn (show b)
putStrLn (show c)
putStrLn (show d)
-- ~/test/E$ ./M
-- Person {name = "Bob", age = 50, height = 72, weight = 190}
-- Person {name = "Bob", age = 60, height = 72, weight = 190}
-- Person {name = "Bob", age = 60, height = 72, weight = 190}
Because player and position are records, the function could actually be written:
moveRight :: Int -> Player -> Player
moveRight n p@(Player {pos = curPos@(Position {x = curX})) =
p {pos = curPos {x = curX + n}}
In which case it isn't making any assumptions about extra properties. The record pattern-matching syntax isn't the prettiest, though, and I find myself having to look it up every time to remember where the accessor name (pos), parameter name (curPos), and intended pattern (Position...) go relative to the "=" and "@".
In the most naive way of doing such things, there is indeed such a tradeoff. So let's say that my Player is now allowed to collect Rice, which he will use in certain eat() functions.
If Player is a data structure, I could add the rice counter to the Player and then modify all of my functions to "pass through" the rice. If the game is small enough, this makes the most sense. This is not as bad as it sounds because the typechecker will get angry about any Player functions in the code which do not have rice. So I've got the language on my side when I want to "make sure I've got all of them," it's not just a haphazard grep for the token "Player".
I could also write everything dogmatically with a pattern matching syntax which passes through all of the rest of the data. This is embodied in the examples below so I won't re-write it.
On the other hand, I can treat the game as a module, create a new data structure `PlayerWithRice {player :: Player, rice :: Int}`. Then I can define `addRice :: (Player -> Player) -> PlayerWithRice -> PlayerWithRice` which takes a function for modifying players without rice, and turns it into a function for modifying players with. Then there's just a list of new declarations in a new module which add rice to existing code.
This all sounds complicated! Surely there's something simpler? There are a few simpler ideas: one is called a Monad Transformer. The Player can be stored in a slightly more complicated way at first, so that it "wraps" another object which holds "all future data-to-be-added". All of your existing code written in this context will just automatically pass this 'future' data through. For PlayerWithRice you simply fill in the 'future' data as a data structure which holds rice and also more future data.
Second, there are lenses, which the above article discusses. If you write every set/get with lenses, then updating the data structure only requires updating those lenses, and all the rest of the code will follow. Again, you'll have a type-checker on your side to guarantee that you didn't miss anything crazy when you update those lenses.
Lenses are a way to try to minimize that effect, because it does "kind of" happen. This is one end of what Wadler termed the expression problem, essentially most languages are structured such that either the nouns or the verbs are complex since if they both are then you have n^2 interactions.
Haskell chooses to make functions complex to enable simpler data types. This means there's a lot of drive to have useful, abstract interfaces, but it also means that you have to protect against the kind of complexity you get when expanding your objects.
Lenses help to do this by abstracting "views" on objects so that you only have to update the lens... and that can be done automatically.