5 ms·
I still don't get how people can claim that functional programming is inituative when mutating a nested struct requires a separate package and metaprogramming h
by verroq 13y ago
I still don't get how people can claim that functional programming is inituative when mutating a nested struct requires a separate package and metaprogramming hacks.
- sfvisser 13y agoIt's a trade-off. You can either use direct mutation using some sort of impure reference type, or use a pure mutation using first class accessor labels. Impure references are probably easier to use, but are harder to reason about and allow for more subtle bugs in your code. Accessor labels require a bit more trickery, but give a more principled approach and result in code that is less error-prone and easier to compose. Haskell doesn't force you in any of those directions. Pick the approach that bests fits your domain and likings. Personally I'm convinced pure code is a way better default, however for certain domains I certainly resort to using `IORef`s, `TVar`s, etc.
- jbri 13y agoWhy do you need to mutate things in order to write a program? Operational programming may have damaged your brain. (With regards to lenses, note that: This isn't mutating anything, and it's not a metaprogramming "hack" either. Functions as first-class entities instead of opaque blobs of code is one of the big wins of functional programming.)
- marshray 13y ago> Why do you need to mutate things in order to write a program? To change the state of the universe from one in which my program is not written to one where it is. Furthermore, if the program I write mutates nothing upon execution there is no point in running it.
- Bogdanp 13y ago| mutating a nested struct requires a separate package and metaprogramming hacks. But it doesn't. And you would have known this if only you had read the fucking article. data Position = Position { x :: Int, y :: Int } data Player = Player { pos :: Position } moveRight :: Int -> Player -> Player moveRight n (Player (Position x y)) = Player (Position (x + n) y)
- fdej 13y agoEvery time you add a new property to Player, wouldn't you be forced to update all such functions to pass along the extra properties?
- bjterry 13y agoBecause 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 "@".
- knome 13y agoNah. 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}
- drostie 13y agoIn 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.
- ocharles 13y agoThere is no metaprogramming here. You may not be familiar with a style of programming, but that doesn't entitle you to spread FUD about it.
- verroq 13y agoTo uses lenses with record types usually one would generate the lens methods using template haskell.