5 ms·
>Sure, there have been games written in Lisp and some games written by language dilettantes fond of Objective Caml, but they never turn out to be programmed in
by savanaly 6y ago
>Sure, there have been games written in Lisp and some games written by language dilettantes fond of Objective Caml, but they never turn out to be programmed in a functional style. You can write imperative code in those languages easily enough.
I would like to know more about this. I've written several games in Elm, a js counterpart to Haskell, and I guess foolishly assumed that meant I was writing them in the functional style. Now I'm curious if I'm actually writing in an iterative style and if so what would the functional style look like? I would welcome general responses to this as well as any comments on the code style specifically in examples like [0].
[0] https://github.com/tristanpendergrass/legendary-barnacle/blob/master/src/Main.elm#L241-L279 https://github.com/tristanpendergrass/legendary-barnacle/blo...
- cmrdsprklpny 6y agoMy understanding (which there's a good chance is incorrect) is that all code written in Elm has to be in a functional style because the language strictly enforces it; but this enforcement doesn't exist in Lisp and OCaml, so they can be used to write imperative or object-oriented code, as happened in the cases described.
- ImprobableTruth 6y agoHaskell enforces purity, but you can still write pretty imperative looking code by using monads (e.g. some state monad). I don't think there is any philosophical difference between say int x = foo(); bar(); char y = baz(x); and do x <- foo bar y <- baz x I'd say the true difference is more related to how you describe state change. Imperative is about having a sequential list of instructions that all can mutate global state in some way, whereas with a functional style it's about composing functions that depend on their arguments rather than global state. Functional purity lends itself to a functional style, but you can just explicitly pass a large (global) state around and compose functions only sequentially, which imo lands you right back in imperative territory.
- elbear 6y agoHow would you handle state other than passing it around? The fundamental thing that Haskell and Elm do is that they don't have mutable values. They create a new value from the old one. You never mutate a record the way you mutate a JS object or a Python dict.
- ImprobableTruth 6y ago> How would you handle state other than passing it around? By only passing and updating the relevant bits of state. >The fundamental thing that Haskell and Elm do is that they don't have mutable values. They create a new value from the old one. If we're talking about the context of a game, you'll be very likely using monads where this isn't the case (IO, ST). Not to mention, even if you do just use a normal state monad, if you don't keep the old value around, there's no functional difference between mutating the entire program state and creating a new program state while forgetting the old.
- dddbbb 6y agoI'd say there is quite an important conceptual difference in that the state monad example exactly desugars to pure functional code. Monads give us a good syntax for sequential computation, but this sequentiality is built out of purely functional components.
- lalaithion 6y agoThe State monad desugars to pure code, but the IO monad doesn't. If you use IORefs or MVars, then you do actual, real mutability by changing the value at a location.
- makapuf 6y agoYour answer may be in the comment just after: in elm you explicit the state, you isolate it and you can reason about its structure, but there still is a state.
- JD557 6y agoI think it helps if you don't think about a single axis (functional vs imperative), but in two axis (functional vs procedural, declarative vs imperative). When you use IO with do notation, you are actually doing imperative functional programming.
- recursivecaveat 6y agoFrom my, admittedly hobbyist, perspective, I would avoid writing game logic like this without using the command pattern[0]. Whether you're impurely updating values directly or purely inputting and outputting the game state, without commands your function is always a black box. This is more convenient initially but when everything is a black box it's difficult to write 'meta' effects correctly. For example in a card game you'd often have things like 'if this happens, do that as well/instead', do effect X twice, change the target of X to Y, etc. If you diligently implement everything as "state -> [command]" , then meta stuff behaves a lot better, as does undo/redo, logging, and debugging. Note that some might scoff at explicit command structs and write everything as simple closures, but then you get none of those benefits. While the linked example is from an OO language, I think it is more functional than the "state -> state" style, because everything except the commands themselves just does computation to produce values instead of transforming a big state bag into another. [0] http://gameprogrammingpatterns.com/command.html http://gameprogrammingpatterns.com/command.html