13 ms·
Admitting That Functional Programming Can Be Awkward (2007)
- nicolashahn 6y ago> admit that functional programming is the wrong paradigm for some types of problems. Absolutely. Some problems are just so much simpler in an imperative style that it's worth the lack of confidence you get with the functional solution in its correctness and robustness. That's why I love languages that have fairly powerful functional features but have an imperative escape hatch. I think more and more languages are becoming that. Rust does it very well, but the lisps, probably Scala, and a few others I'm not thinking of may be better, don't have much experience with those though.
- voodootrucker 6y agoRust is good, Kotlin is the best I've seen at it.
- emteycz 6y agoIMHO TypeScript is getting really good now, in the 4.1.
- bigyikes 6y agoTypeScript is such a joy to work with. It captures the ease-of-use of JS but erases many of the wat-isms associated with it.
- atty 6y agoI think anyone who has coded long enough would recognize that OOP, functional, imperative, etc all have their strengths and weaknesses, and all have problem domains they are better suited for. Unfortunately, this sometimes gets lost in the language fan wars - it’s easy to lose nuance when discussing something you are passionate about, I have made this mistake myself.
- belval 6y agoI'd go one step further and say that most people should care little about language features when doing an actual production project, as there are aspects such as how proficient your team is in a given language and how easy it will be to maintain afterwards that are simply more important. That does not mean having a favorite paradigm or language is useless, of course I love Rust and I find functional programming elegant, but if you are working in a 5 years old app written in C# in a place where no one knows F#, then that should be part of your considerations.
- shaan7 6y ago> how proficient your team is in a given language and how easy it will be to maintain afterwards that are simply more important. That is a double-edged sword. On one hand it makes sense because it allows teams to be more productive. Otoh, it leads to this decade where it has become okay to create a desktop application using HTML/JS (while more performant tooling exist) simply because a lot of people happen to know HTML/JS than C#/.NET, Qt/C++ etc. What should be just one of the factors has become the most influencing factor.
- qsort 6y agoI'm all for the 'right tool for the job' mindset, but why would it be bad in principle to write an application in JS? Leaving aside the fact that modern JS is ~3x slower than C++, i.e. slightly slower than C# and Go but in the same league as Java/Haskell, not in the same league as Ruby and Python, programs like VSCode are written in JS and they have no performance issues. It's true that performance is often ignored for no good reason at all, but it's not such a pressing concern for most applications. Very little software needs to be performant to the point that what language it's written in even matters.
- jcelerier 6y ago> programs like VSCode are written in JS and they have no performance issues. here it is compared to qtcreator. it is so damn frustrating when you are used to computers responding near-instantly to have something that... takes its time or stutter to say the least https://vimeo.com/410735827 https://vimeo.com/410735827 https://vimeo.com/410739729 https://vimeo.com/410739729
- kibwen 6y agoI think it's time we stop using the phrase "functional" to describe this paradigm; at this point, every imperative language made in the past 30 years has first-class functions, and there is no other first-class language feature common to all the self-described "functional" languages. IMO, a more accurate term would be stateless programming. This paradigm is about minimizing state. Of course, as the OP mentions, state is often quite useful. But minimizing state, especially global state, particularly mutable state, and especially particularly global mutable state, is something even imperative language fans can get behind.
- Avshalom 6y agoIt's a bit more subtle but the lack of statements that is, everything returns a value, is an important feature/quality.
- heavenlyblue 6y agoExpressional or equational programming
- gnulinux 6y agoFunctional languages do have statements. a : Int -> Int a x = x + 2 both `:` and `=` are statements, not expressions.
- fiddlerwoaroof 6y agoThis is sometimes true, the `=` equivalent in Clojure is an expression, though: user=> (def a (def b 2)) user=> a #'b Lisps generally only have expressions (every form returns a value.
- gnulinux 6y agoLisp runtime implicitly runs each top-level expression as statement. When you run (def x y) it's not only true that this expression returns 'x, it's also true that it alters the static state of the program by adding 'x to the scope. To make a purely statementless language, I think you need to make everything happen in a single monadic computation. E.g. in pseudo-Haskell-ish: main : Scope -> StaticIO Scope main scope0 = do scope1, x' <- def scope0 "x" Int scope2, xv <- dec scope1 "x" 3 But the main part is still statement.
- r-w 6y agoEver heard of the Elm Architecture? It helps address some of these pain points, e.g., by forcing you to decide from the get-go what your global modifiable state comprises, and then having each action consist of an update to that state model.
- 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.
- voodootrucker 6y agoI wrote an exercise once while I was teaching: "pure functional pacman". Really I did it to teach myself Redux, and it was a pretty fun way to learn. Totally agree with the article: sometimes functional is the right tool for the job, sometimes OOP. A more recent example: parsing USB descriptors in Rust. The parser would have been trivial and easy to understand if I could have multiple mutable references to nodes, but alas I kept fighting the borrow checker. I talked with a friend about why it was so hard, and he said "do it in a functional style". That made my borrow checker worries go away, but instead I have multiple "tree pivoters" that recursively descend through input trees while building output trees. It practically broke my brain and although it is pure, it's not nearly as readable or maintainable as one with mutation would have been.
- marcan_42 6y agoOff topic, but may I ask what USB stack you're working on? Because it might have a chance at being the first USB stack not full of exploitable bugs. (I've deliberately and accidentally triggered bugs in pretty much every mainstream USB stack; it's a sad state of affairs, mostly because USB is practically designed to include the maximum number of implementation pitfalls... but I'm sure you know this already)
- voodootrucker 6y agoI'm talking to a bunch of UVC webcams using https://docs.rs/rusb/0.6.4/rusb/ https://docs.rs/rusb/0.6.4/rusb/ for some conferencing software. So sadly no help for your (very valid) concerns.
- dehrmann 6y ago> sometimes OOP. I think OOP is taught somewhat wrong because people will dwell on contrived examples of inheritance when its real value as a paradigm is coupling state with functions. What we've learned from FP is immutability is easier to reason about, but when it isn't is when OOP shines because it gives you a sane way for managing state.
- 6y ago
- deleted 6y ago[deleted]
- jasperry 6y agoCould it be the case that functional structure makes programs more reliable and easier to reason about in the large, but functional style/syntax may not be the best way to write code locally? Trying to wrap an entire function's computation into one expression is a fun mental exercise, but the resulting code may be harder to read than a sequence of statements. Haskell's "do" notation seems to be an acknowledgment of this, but to me it's adding another layer of abstraction just to recover what imperative syntax gives in the first place. Why not start from the imperative model, and then find other ways to try to limit side effects and mutation? Languages like Rust may be heading in that direction; "Return of the statement" as it were.
- mrloba 6y agoI completely agree. Just write pure functions as much as possible and you're good. What goes inside a function doesn't matter so long as the function itself is pure, imo
- mrkeen 6y agoDo you have any examples of compilers or linters that assist with this? Any kind of error or warning would do.
- tome 6y agoHaskell has ST[1], which allows you to do mutable in place updates in a way that the compiler can guarantee are not visible from the outside. It's not particularly popular though. Typically it seems to be more convenient to either be thoroughly pure or accept mutability and use IO. [1] https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Monad-ST.html https://hackage.haskell.org/package/base-4.14.0.0/docs/Contr...
- jacinabox 6y agoThere's also the probably underappreciated packages ref-tf and ref-fd which let you write code that is polymorphic over the type of state reference. These can make 'ST' more acceptable because you don't have to make the decision of what state reference to use up front.
- dnautics 6y agoI'm confused by this article. The author says he's in erlang (which, btw, is not pure functional). And then goes on to describe a system which sounds like an object oriented system. But a game programmer would probably actually implement it not as object per se, but maybe backed by an entity component system. But it seems like BEAM languages would be actually quite good at expressing entity systems, which, correct me if I'm wrong, are similar to say checking out and manipulating data in a database.
- ramchip 6y agoI think it’s a common trap for Erlang beginners to try to represent all entities (player, monster, room, etc.) as processes sending messages to each other, the way you’d use classes in OOP. It’s one way to learn the concepts, but in real apps it leads to messy, brittle code. Erlang processes really shine when they’re used as units of fault tolerance, not a code organization tool. For an entity system ETS could be interesting, but it involves copying data on every read/write which could be a problem in games...
- MauranKilom 6y agoWhat do you use for code organizational purposes then?
- dnautics 6y agoModules. Basically something between a namespace and a shared object (.so)
- dnautics 6y agoSure. You wouldn't use pure erlang for a game where perf is critical, but not every game is like that. I bet you could even write a fantastic AAA game by carefully dropping into NIFs as necessary. I think though my argument is that you also wouldn't use objects for that sort of thing either. The brittleness of the code has nothing to do with the underlying vm, or its performance, or even it's concurrency, and everything to do with the code architecture and data organization, which is isomorphic between genservers and objects.
- mrkeen 6y agoThis article implies that functional languages cannot run imperative statements, even though it takes for granted that imperative languages can call functions. Google "world's finest imperative language" for a different take on this. > And each of these is messier than it sounds, because there are so many counters and thresholds and limiters being managed and sounds being played in all kinds of situations, that the data flow isn't clean by any means. > What's interesting is that it would be trivial to write this in C. You could make the same argument about dealing with git and all its crazy branching, rebasing, and reflogs, compared to just editing your source code in place. Or double-entry bookkeeping versus just keeping track of how much money you have. It gets awkward. I loved the elegance of game programming in Elm a few years back, but the performance was dogshit - in my case at least - so I probably wouldn't do it again. The real reason to stick to C/C++ in game programming is speed.
- pyrale 6y ago> I loved the elegance of game programming in Elm a few years back, but the performance was dogshit - in my case at least - so I probably wouldn't do it again. There's always going to be limits to what you can do in a browser using Elm, but you may be interested in this talk from 2019: https://www.youtube.com/watch?v=_flFAV0_TeY https://www.youtube.com/watch?v=_flFAV0_TeY
- dexwiz 6y agoThe middle part about insects, their spawn rates, types, and mutating values is all about state. No matter how much you try to go "stateless", there will be some state implied by your data. Functional programming as the author describes it is good at calculating state, but bad at accessing it. The only for functions to access state is through parameters, which have a final single value in your closure (no out parameters please). They don't have registers/variables/properties to modify. Functional programming says values like spawn rate or type should be the output of an expression. Monads try to solve this, but storage must be the output of an expression, and is always more complex than simple assignment. There is one more secret about programming that functional tries to ignore, that it runs on physical machines. Even though some would like to describe programs pure mathematically, they still are bound by physical mechanisms. Most of those mechanisms are for storing state in memory or transferring state to another location. Abstracting over this fact makes programming painful once performance is taken into account.
- pg_bot 6y agoOnce you grok the language you can easily model the type of situations that they find awkward. For example in erlang/elixir you can keep game state in a series of processes. You model the state changes based on messages that a process receives, and you can spawn/terminate processes as needed. Everything he described as challenging is trivial if you understand the language paradigm.
- jqpabc123 6y agoThere is no one "best" way to program.
- eyelidlessness 6y agoHonestly I think this conclusion comes more from lack of familiarity with how people solve similar problems with FP than anything. For example, in the author’s followup post: All you have to do is pass the world state to each function and return a new state. True, yes, but...yuck. It can be clunky in a language with single-assignment. And what is this really gaining you over C? I mean, if you stop there, sure that’s ugly. But if you model your program with that as the foundation, then break it down (and generalize where breaking it down is general), it’s pretty easy to reason about. And what it “gains you” is never having to think about something changing in a way that produces invalid or unexpected state. (This is more true in statically typed languages of course.) In any program of any real complexity, you will eventually be inclined to break the problem down into smaller pieces. If your smaller pieces are functions, you can be certain about the state that’s returned by them. If they’re stateful subroutines, then you have to think about multiple pieces at the same time.
- treis 6y ago>And what it “gains you” is never having to think about something changing in a way that produces invalid or unexpected state. (This is more true in statically typed languages of course.) I think it's more accurate to say that it makes you account for the possibility that an object is put into an invalid state at every mutation you do. Which can be good and can be bad. Good being that your code is safer and less likely to have errors. Bad because it's more rigid and trickier to deal with objects containing multiple errors.
- andi999 6y agoI dont understand this. This has the same disadvantages as global variables: every function (that has the state) can change it.
- IshKebab 6y agoRight but global variables are implicit. Function inputs/outputs are explicit. Another advantage is testing. That said, I still think passing the world in and out of every function is too onerous for most real world programs, and functional programming definitely takes a too-extreme stance to be pleasant. I think a better approach is a traditional language but with a functional subsystem where you can mark functions as pure.
- tome 6y agoCan someone with experience of entity component systems (ECS) please chime in? My understanding is that games are not written in the manner that the author says is so easy, rather they are written as pure functions operating on a big table of world state. The world state happens to be mutable for efficiency, but that's an implementation detail. Then again I've never worked with ECS so perhaps this my misconception.
- Jare 6y agoECS changes the way you access and mutate the world, compared to classic data graph / reference tree, more classic pervasive singletons ("managers"), or even more classic straight global variables. But in ECS engines you still access a portion of global world state you're interested in, and you mutate some portion of it. The ability to declare what those portions are, when present, allows ECS engines to do stuff like parallel execution of systems, and it also prevents a host of data races, but imho there's nothing really functional / pure / immutable about it.
- imtringued 6y ago>pure functions operating on a big table of world state Who told you that? Sure you can simplify it as a function that is operating on a big table but there is no requirement for it to be pure. An attacking Entity A can mutate the HP values in entity B directly.
- FpUser 6y agoOf course it can be awkward as it is very specific and opinionated paradigm. It may help to solve some problems while being totally unsuitable to solve others in a sane manner. There are no silver bullets in life. I personally always shied away from adopting/committing myself to any strict concept. My long experience taught me that as soon as you do there it will likely blow up in your face one or the other way some time down the road.
- mikewarot 6y agoProgramming without destructive updates works because it makes explicit the data flow in a program, not because destructive updates are bad.
- karmakaze 6y agoThe direct update imperative way works until you have enough multithreaded actions that interact in undesirable ways. In addition, locks don't compose. I worked on a relatively straightforward trading app with charting and realtime rates. There were so many bugs from direct updates that interfered with rendering that it was best to restructure it as world-in, apply function(s), world-out. Then the renderer only had to deal with one frame of the world. Some items had a smaller 'world', like a single price tile. Whether done in a functional language or a procedural one this functional pattern is a common one.
- kaashmonee 6y agoI'm not sure I'm understanding the point of this article. Every language and language paradigm is awkward if it's used for purposes it's not meant for. Writing a game is something that is really heavily dependent on mainting some sort of state and functional programming in principle is supposed to be stateless. So, unsurprisingly, it's awkward to use FP for game development.
- nerdtime 6y agoFunctional programs tend to be more modular than imperative or OOP counter parts but nobody really knows why nor do they understand the cases where FP becomes less modular. FP is only modular when you use combinators. If you use closures then it's no longer modular. f x y = x + y g y = y * 2 w x = (f x) . g g and f are combinators and modular and w is the composition of both of those combinators. w = \x -> (\y -> (x + y) * 2) In this case the above is not modular because it doesn't use combinators. The above style is actually kind of promoted by haskell when you need to do things with side effects. It actually makes FP more complex than it needs to be without improving modularity.
- tome 6y agoHuh, what do you mean by "modular" that isn't satisfied by the closures?
- nendroid 6y agoBoth examples have multiple functions defined as the definition of w. But in one example f and g can be reused in other contexts, in the other example the two functions are tied together by free variables. Haskell heavily promotes the latter style with do notation. Any function written in the first style is decomposable into component combinators, any function written in the latter style cannot be decomposed.
- chowells 6y agoI would much rather write the logic the author describes in Haskell than C. The C code will have to be carefully managed during development and maintenance to make sure the side effects are happening in the expected order and that no changes ever introduce unexpected interleaving of mutation. The Haskell version will prevent that trivially. You can't even express doing it the wrong way, because you can't mutate anything. Accidentally interleaved mutation is not a theoretical or academic problem. It's probably the number three source of production bugs in my dayjob's various products, behind misunderstood requirements and web browsers constantly changing the rules. It turns out that everyone, even people like me who know better and have been burned several times, will sometimes take the convenient shortcut. It's so tempting to get something done immediately by quietly mixing some mutation in an unexpected place, and it usually doesn't bite you. Then it gets ossified that way, and a year later starts biting you. This really does happen in code maintained by multiple developers over multiple years. To be fair to the original post, though, Haskell has come a long way in making that kind of coding easy since 2007. The lens library didn't exist back then, and it's a big part of why data transformation programs are so much more pleasant in Haskell than most languages. It lets you express data access at the right level of abstraction. You get laws to enable algebraic reasoning and a broad range of utilities for composing small pieces together to solve complex problems.
- kkdaemas 6y agoOrder of execution bugs rarely come up in game programming to be honest.
- chowells 6y agoIf that's true, why is it an issue John Carmack discusses as affecting difficulty of writing games that work correctly in modern environments? > When you start thinking about running, say, all the characters in a game world in parallel, it starts sinking in that the object-oriented approach of updating objects has some deep difficulties in parallel environments. Maybe if all of the object just referenced a read only version of the world state, and we copied over the updated version at the end of the frame… Hey, wait a minute… https://gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php https://gamasutra.com/view/news/169296/Indepth_Functional_pr...
- flowerlad 6y agoWhat is worse is when some developers maintain and mutate global state, write functions with side effects, and still think their code is "functional" because they aren't using any classes. A lot of JavaScript developers are moving to that camp because of React and Hooks. More on that here: https://medium.com/weekly-webtips/dysfunctional-programming-in-javascript-cae5c085a76e https://medium.com/weekly-webtips/dysfunctional-programming-...
- pmarreck 6y agoHe thinks passing around the world state as a functional immutable data structure is “gross”, but modifying the same state as a global from anywhere in the program... isn’t?
- ookdatnog 6y agoJohn Carmack commented quite extensively on functional programming in video games: https://youtu.be/1PhArSujR_A?t=125 https://youtu.be/1PhArSujR_A?t=125 https://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php https://www.gamasutra.com/view/news/169296/Indepth_Functiona...
- ngcc_hk 6y agoIn spite of its name this video use JavaScript to implement a game using the three styles of programming. Great video learnt from hacker news: https://youtu.be/vK1DazRK_a0 https://youtu.be/vK1DazRK_a0