4 ms·
It seems at least some of the difficulties described are managed better in Haskell. For example, rebinding to the same name (as an alternative to destructive u
by Peaker 10y ago
It seems at least some of the difficulties described are managed better in Haskell.
For example, rebinding to the same name (as an alternative to destructive update) can be handled conveniently and nicely via state monad and the lens library, with syntax roughly like:
pacman.position += speed
This is still purely functional because the resulting type is roughly: State Game () which is equivalent to: (Game -> Game) (a pure function that takes a game and returns a modified game).
- jstimpfle 10y ago> This is still purely functional because the resulting type is roughly: State Game () which is equivalent to: (Game -> Game) (a pure function that takes a game and returns a modified game). This is the kind of arguing that could easily be extended to C and other "impure" languages. And even the IO monad is "pure" -- it's just values containing computations executed by the runtime (i.e. the computational side-effects are not part of the language / type system) Using the State Monad for += syntax is no better than doing the equivalent in C. The real enemy is accidental state. That is, meaningless (but significant) dependencies between conceptually independent calculations. For example: pacman.position += speed pacman.lives = ghostAt pacman.position While updating the position based on the speed and deciding whether pacman was hit by a ghost are conceptually independent, the outcome of the above code is dependent on the order of these lines. Instead it's often cleaner to do something along the lines of pacmanLives :: Pacman -> Ghosts -> Bool pacmanNewPosition :: Pacman -> Position -- use both of these functions in the update routine. -- Hand them the pacman from the last frame -- (i.e. don't "chain" the functions) This way a dependency on the "order" of these computations is "avoided" (kind-of. There is still an order between the frames, but that is not as "accidental").
- Peaker 10y agoYou cannot implicitly read pacman.position as a subexpression like in C, because it is an effect, and the order of effects has to be explicit in Haskell. So it would have to look like: pacman.position += speed pos <- pacman.position g <- ghostAt pos pacman.lives .= g Though a dead Pacman probably has no position, so it makes more sense that the last line is: when g (pacman .= DeadPacman) Messing up the order because of implicitness becomes much harder.
- jstimpfle 10y agoWhat you say (re: subexpression) is true in Haskell. (I was of the impression that "pacman.position" wasn't valid Haskell anyway, but now I think it is. Never had a serious interest in lenses). However that's a cosmetic issue. The point still stands: If you chain ("use the State monad"), the outcome depends on whether you update the position or the liveness first.
- Peaker 10y agoIt does, but unlike impure languages, this ordering is not implicit in the evaluation order -- but explicit. In the same sense, if you compose the pure functions: updatePacmanPosition . checkPacmanAlive vs: checkPacmanAlive . updatePacmanPosition Will exhibit the exact same issues. The state monad merely lets you more conveniently write it more like: do updatePacmanPosition checkPacmanAlive Where the do stmts are (sort-of) composed together as functions. So the state monad here is not the issue -- it is sugar around function composition. The issue is whether the ordering of these manipulations is explicit via data-flow or statement ordering, or whether it is implicit in evaluation order applying to shared, mutable state.
- jstimpfle 10y agoYes, that was actually my point. State is essentially function composition. Thus I'm arguing for avoiding dependencies between conceptually independently compositions.
- Peaker 10y agoIt's true until the composition and data flow are hidden from view because they're implicit in the shared mutable state. Also aliasing issues don't occur with the composition as they may occur in mutable state.
- marcosdumay 10y agoThey are equivalent if you place your entire program into some StateT a IO monad, where a is the contents of the entire memory accessible within your program. That's to say, you do have a point, but both too big states and too little pure code are well recognizable smells. (I' not sure they are avoidable, but surely are smells.) On that sense, just by being easily recognizable they are already better. I've been writing a small game in Haskell, and it has been funny to abuse monads and type classes for all sort of architectural features. But it's impossible to look at the code and mistake it for a well organized one.