Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Admitting that Functional Programming Can Be Awkward (dadgum.com)
87 points by iamelgringo on June 16, 2009 | hide | past | favorite | 60 comments


Right now I'm trying to learn clojure, and write 2d arcade game with it. I've encountered similar problems (almost everything should be passed almost everywhere), but I think this is actually good thing to choose what part of game can change what beforehand.

My architecture (so far - it's in early planning stage): 2 threads (physic and logic thread going 10 Hz and graphic thread going as fast as it can drawing world)

objects are struct-maps with fields position, animation frame, life etc (all fields are functions of time, and can change between physic thread turns, but not in one physic thread turn).

Graphic thread just gets time and for each visible object draws it at current position (calculated from time by objects position function). So movement should be smooth and not dependent on frame rate.

physic thread in response to player input calculates new functions for objects (only for X next turns). These new functions are encapsulated in event objects, that are added to events priority queue.

On each turn (of physic thread) events for that thread are executed - each event takes set of game objects (not always all objects) and time and returns changed set of game ojects.

In C++ this will be much easier to write at first (no hard thinking needed:)), but later it could become nightmare to change because of side effects.


It sounds like you are overengineeing it.

Modern computers are fast; Program it naively and if you see it's too slow (which it probably wouldn't be) optimize the bottlenecks.

I am pretty sure you wouldn't need to make this multithereaded.


I know it's over the top, but it's to try sth different for once. Also - multithreading is because my earlier games was in one thread, and I've always had problems with consistent framerate - some frames was longer, some shorter. When I tried considering last frame duration as multipilier, game became not stable (too long frame and bullets will fly throught the wall, etc). And networked game is difficult to get right with nonuniform duration of frames. So I try to see if this will be better.

What's for sure overenginered is using function of time instead of simple numbers for positions. But that is just to make it different.


Perhaps he's more interested in experimenting with this kind of architecture?


I definitely consider it an overreaction to call the described approach over-engineering. Separating simulation and presentation in to separate threads is common and very practical in many games (2d or 3d). It is also common and easy to support a single threaded mode that just runs both simulation and presentation update from one thread. The single threaded mode can make debugging certain issues much easier, but it is rare to have a desire to ship single threaded.


i thought it sounded pretty clever


"Clever" and "overengineered" often go hand in hand :)


But lets not lose sight that without a little bit of cleverness and overengineering we'd still programming in machine code. Also it seems to me that pretty much every popular programming language out there (PHP, C++, Java, Python, Ruby, Erlang, etc.) is the result of overengineering and cleverness.

The few languages that aren't overengineered are the ones that either have a very specific domain (JavaSript), slow to change standards (C), or are very new (Clojure).

OP's idea also doesn't sound very over engineered to me. The whole draw loop thing is simply a convention and not an obvious one. I've taught many a student who stumbled on the idea of the render loop because there's simply nothing natural or obvious about it.

In the real world everything happens at once, there is no update-render-loop. Snapshoting the state of a multithreaded physics engine at discrete intervals in order to render the game state is much closer to a model of how we perceive a fully concurrent reality.


in this case it's a good way of getting smooth animation specially in a dual core whatever whatever :)


This sounds really interesting! Any chance you want to throw it up on github etc?


Thanks, yes, I'll release it when it's ready.


Notable quote:

That's one option: to admit that functional programming is the wrong paradigm for some types of problems. Fair enough. I'd put money on that. But it also may be that almost no one has been thinking about problems like this, that functional programming attracts purists and enamored students.

The language features that help with the problems small programs have are intrinsically easier to show off than those that help manage larger systems. Beyond a certain point, I don't care how easy something makes a program that will never grow beyond a page, though. (If that isn't easy, your language has serious problems!) I'm more interested in how these techniques / paradigms break down, not how well they work on examples chosen for them. You can't always choose your problems.

(And yes, one of the main things I've learned from Unix is that it's usually better to break large systems into a team of smaller components, but that's neither here nor there. Also, informed use of multiple paradigms is likely the best long-term approach, but that's not a position that necessarily translates well into sound-bytes.)

Of course, it's easier to hype languages that let you do short parlor tricks, particularly on web forums.

Also, he has a followup posted here: http://prog21.dadgum.com/4.html


languages that let you do short parlor tricks

Quite the opposite - functional programming is becoming very appealing as a practical alternative for building large, multi-threaded, correct systems.

True, it might be a bit more tedious to write a program that type-checks, but as many (including me) have been repeatedly amazed, it often works correctly right away, with minimal debugging.

But it also may be that almost no one has been thinking about problems like this

Mutable state is a fundamental issue that every functional programmer deals with every day. Indeed, it might be sometimes frustrating to pass around state that in C would be directly modifiable, but the benefits are huge.

Pure code has been called a "scarce resource", and that's exactly right - once it works, it will always work, no matter how huge is the system you build on top of it.


OCaml is one of my favorite languages. I use it, despite its many warts, because it works. (I think there's a much smaller, cleaner language waiting to get out, though.) I wish more languages had decent module systems, for example. The ML family does this better than most, but I bet it's not a very glamorous area to experiment in, because it's hard to really show the benefits of a well-designed module system in small examples. That's what I'm complaining about.

BUT, pure functional programming is an intrinsically poor fit for some problems, and this is seldom addressed by hype. I think functional programming hype needs a reality check now, the same way C++-style OO hype could have used more way back when. I think we would all be better off if, instead of heralding it as the next technique that solves all problems, we work on getting a clearer idea of its strengths and weaknesses.


Oh, I agree with the strengths and weaknesses, and obviously I don't use FP for all my programming.

But I somehow cannot see any functional programming hype. Where are the people heralding it as solving all problems? Even in a place like HN with a highly non-average community of programmers, the FP links are quite rare and very intro-level. I would say FP is a high-hanging fruit that's only starting to find a few niches in real-world programming.

So I son't see anything close to an FP hype, nowhere near to the Java hype or Ruby hype.


Reddit has an ...ardent community, and I suspect regulars there may gain an unrealistic impression of the amount of FP hype there is out in the wider world.


I'm disappointed that I have yet to see a decent discussion on HN on comparing the ML-style module system with OOP. It's really worth talking about.

Also, OCaml is sometimes called a "higher-order imperative programming language" instead of a functional programming language. It does have strict evaluation and first-class mutation, after all. I would argue that there is NOTHING awkward about writing code in it, controlling for libraries and development tools.


I've wondered what the real difference is between a parametric module system that allows you to substitute along common interfaces, versus an object system that allows "duck" typing, mixins, or interface inheritance. In practice, the difference is that the former comes with type inference. :)

I think it'd help if there were more books on OCaml. The French O'Reilly one is pretty good (and free online, http://caml.inria.fr/pub/docs/oreilly-book/), but the Joshua Smith one is awful, and there aren't many others in English.

Also, the OCaml syntax scares some people, and its error messages are pretty unclear, particularly if you haven't learned how to work with the flow of H-M typing. These (and other similar issues) aren't fundamental problems with the language, though, just rough edges in the implementation.


> I've wondered what the real difference is between a parametric module system that allows you to substitute along common interfaces, versus an object system that allows "duck" typing, mixins, or interface inheritance. In practice, the difference is that the former comes with type inference. :)

In OCaml, I can create a module with multiple types, hide information about both of them, and write code sees both of their privates at the same time. In OOP, this can't be done as elegantly (only through the use of inheritance, friends, or inner classes). And this is just the tip of the iceberg; there's a lot more flexibility.

Books would help, as would improving the quality of the error messages (which are literal translations from French). However, neither of these are problems with the language itself... you can improve them without changing a single line of code or iota of the specification.

Type error messages will with HM always be hard (I think understanding an OCaml error message is a PSPACE-complete problem). But, Haskell has great type error messages, so again, this can be improved.

However, I still stand by my claim that the language itself doesn't force programs to be awkwardly structured, as is claimed throughout this discussion.


Indeed, it might be sometimes frustrating to pass around state that in C would be directly modifiable, but the benefits are huge.

Such as? Ok, so you get better multithreaded behaviour but now you're copying state all the time and having to thread it though all your functions. Sometimes just updating state is the right thing to do.

Personally, I think a good tradeoff is immutable data but mutable references.


The main benefit, as I mentioned above, is that if a pure function works once, it works all the time. This is captured by the saying "in Haskell, if it compiles, it works", which is surprisingly often true.

If you have a function that moves a player in a game such as movePlayer :: PlayerState -> PlayerState, you know that it only changes the state of this player, and nothing else. In an OO language you would do something like player.doUpdate(), which has absolutely no guarantees.

Plus there are many facilities available to deal with threading and hiding state.


> you know that it only changes the state of this player, and nothing else.

This gets ugly when the state of every actor can potentially affect the state of every other actor, though. This is pretty common in games.

That's the main point the linked article is making.


Yes, and it is a weak point.

My point, instead, was that FP does not break down when things get ugly; on the contrary, it gives much stronger guarantees on correctness. Of course, it takes effort to express the problem in the more restrictive functional paradigm.


Yeah. I fail to see how scads of global data is less ugly.


Beyond a certain point, I don't care how easy something makes a program that will never grow beyond a page, though. (If that isn't easy, your language has serious problems!)

Maybe we can call your thesis "verbosity is power".

A page of COBOL is easy to understand and easy to write; a page of APL is much slower to understand and fiendishly difficult to write. That doesn't mean you should prefer COBOL over APL. It's a result of the fact that you can express more in a line of APL than in a page of COBOL. And, in fact, that line of APL is easier to write and easier to read than that page of COBOL.

Ideally, any time you're solving a problem that's a small variation of a problem somebody has already solved, you should be able to write your program by referencing their solution and describing the variation. In such a world, no program is more than a page.


Whoa, whoa, whoa. You're arguing with what you think I'm saying, not what I'm trying to say.

There are diminishing returns in focusing on extreme brevity. If I'm going to write a script, I just use Lua. (Other people like Python or Ruby. Sure.) Squeezing 10% more out of an already small program is well past the point of diminishing returns.

Depending on the problem, APL/J/K might make more sense. Or awk, make, SQL, etc. (My only experience with the APL family is playing around with J a bit, but they seem like awk in that they gain their conciseness in exchange for being focused on a certain problem domain. That's usually a good trade-off, though.)

I'd rather see more focus into techniques to make larger projects manageable, like module systems, type inference, declarative programming. As techniques, they're intrinsically harder to show off in this medium, though (except for small amounts of type inference, but its payoff increases with project size). That's all I'm saying.


I think I kind of agree with you, but kind of not.

Try looking at it from this perspective: There is really only one program. It is running on the internet and the disconnected nodes that only exchange data by sneakernet. It never stops running. Every bit of software that we think of as a separate program is really a part of this program. What are the techniques that make this program manageable (in the sense that it continues to perform useful functions and people continue to extend it)? I don't think they're module systems and type inference. I think REST, for example, plays a much bigger role. Full-text search of documentation plays a big role. NANOG plays a big role. Most of the things that really matter most in keeping it manageable are interpersonal and institutional rather than purely technical.

But this large program is made up largely of code, and nearly all of that code is made up of individual lines of code. Many of the technical things that help us most to keep it manageable are precisely the ones that allow us to write self-contained modifications more and more incrementally — a page, or a paragraph, or a line of code at a time.

So in that sense, things like type inference and programming higher on the declarativeness scale (e.g. DSLs, libraries) are important in managing the large project precisely because they can be compellingly demonstrated in "programs" of less than a page.


Theres also a related series of articles starting here: http://prog21.dadgum.com/23.html


Good read, but shouldn't be controversial at all. Functional and imperatives both have their strengths, and that's why Lisp has always stayed in the middle btw.

Also I don't think I heard any serious "purists" lately. Aren't we fighting windmills here?


I think his program would be harder to implement in C than in Haskell. It's all about what you are experienced with.

(The usual pattern is "transform :: world state 1 -> world state 2". The transformer can "modify" as much of state 1 as it wants.)


I'm not trying to be snarky, I'm puzzled and interested in learning.

How is passing around world state from one transformer function to another an improvement over traditional stateful programming where functions change variables?


traceability and testability. update world1 is traceable you know just by looking at it what it modifies (sure in the example given it is opaque since it is the entire world) but if you consider that update world is probably broken into update world.player update world.ai etc. Then it becomes easier to trace execution because there is no implicit state. The lack of implicit state also improves testability. I can create a world and send it to update and know that I don't have to do any implicit state priming.


A large, complicated world state for a FP language is no simpler than a large complicated graphic of objects w/ inheritance hierarchies. You see, the inheritance hierarchies are the world state. In either system, you have to model the graph in your head to understand how it works. Complexity is the problem, not referential transparency.


Couldn't you do the same thing by just not using global variables in an imperative language? Then you get the benefits you're mentioning without lots of pointless data copying. You just pass a reference to the world object and you can see exactly what's being modified.


>>without lots of pointless data copying. Think tail recursion. It is easily optimized. It presents a nice uniform interface that forces the right thing. Just look at .net the generated main. It replaces general global scope with an application object global scope that rather encourages "cheating".


It is true that persistence is a default property of functional data structures but with a compiler or interpreter that isn't braindead you shouldn't get "pointless data copying" unless you hold and read both states after the transform. That is, it should be optimized away.


I stand corrected. Don't you still have to do lots of messing around (in comparison to an imperitive language) to simulate state though?


I am still new to functional programming, but so far it seems like I produce two kinds of code. Functions that gather state and functions that modify state. Say in the above example of update_world world1 all this would do is break apart the world data structure and forward the pieces to their respective update functions and then gather the parts in to a new world. The individual update functions either do the break and gather thing or they do the actual computation on a small entity or set of entities. This makes a large number of small easy to test functions that do the actual work and several "middle layer" functions that are easy to test with stubs.

Though it would be interesting hear how people who have been doing FP for a while handle it.


The pure functional world was pretty much impenetrable for me for that very reason ... until I encountered Monads in Haskell. Monads have changed the game completely by providing a very clean encapsulation of side effects in general. There are people who are trying to generalize it even further using what are called "arrows", but just groking Monads in Haskell will give you a new understanding, somewhat along the lines of what learning exterior calculus does to your view of traditional vector calculus.


His followup addresses this, and brings up some examples which he can't work out how to solve with that pattern.


Beginner question: does this involve a lot of memory copies which can result in performance problems. "world state" sounds like every data structure in the program.


which can result in performance problems

"Premature optimization is the root of all evil."

Computers are fast. On my machine, I can allocate, initialize, and garbage collect almost 4G of memory a second. This sort of thing is really fast, and is unlikely to be a performance problem.

When all of your data is immutable, however, you can reuse the data that you didn't change. The canonical example is a binary tree consisting of 2^n nodes. Changing one node only requires you to allocate n new nodes. As your tree gets bigger, the copying overhead goes to 0.

In the case of Haskell, the compiler is smart enough to figure this all out for you. So all you need to do is write correct code, and let the compiler make it work on a machine.

(It's also theoretically possible to reuse structures by mutating them instead of collecting-and-reallocating. You don't do that as part of your game, though, you let the compiler's optimizer do it. It will do it right, you will do it wrong.)


"On my machine, I can allocate, initialize, and garbage collect almost 4G of memory a second."

When everything has to happen in 1/60th of a second, then all that garbage collection is a serious problem. Memory allocation and deallocation is expensive in games, especially on the consoles.

For simpler games, yes, not a problem.


The "you are not Google" principle applies here. Unless you are writing the next hit game for the Wii, you can afford to write maintainable code. If you need every last bit of performance that your (underpowered) hardware can provide, then you need to take shortcuts and hire the 200-member team that that development style entails.


400, in my case.

But the problem is valid. In games every nanosecond counts, which is why the control manual memory allocation offers is so attractive.

It's a problem that even small, single-unit game developers have. On the indie game dev forums, fighting against the .Net/Java GC is a common topic. When the GC kicks in, your framerate usually suffers and you waste productivity with tricks to avoid triggering it. When you have multiple cores, asynchronous GPUs and multi-threaded OSes it affects anything other than the most basic of games.

Having said that, in http://lambda-the-ultimate.org/node/1277 Tim Sweeney (of Epic, developers of Gears of War and the Unreal Engine middleware) says:

"Will gladly sacrifice 10% of our performance for 10% higher productivity" and also "Garbage collection should be the only option"

That presentation is a good summary of the current situation of AAA-games programming (well, three years old, but still) and why Epic are programming in C++ and not Haskell or some other functional language, even though they'd like to. Well worth a read.


clojure is smart about it, and only copies what changed. start here: http://clojure.org/state


His example is amusing to me because state machines are one of my least favorite things to code in imperative languages (especially C). It's so easy to forget an infrequently occurring state-interaction or side-effect. So if you want to be rigorous you end up with a meticulously detailed switch block (or whatever the idiom is in your language) with a lot of repeated code and global variables everywhere. My kingdom for a few closures!


can you outline a sketch? closures by themselves do not guarantee the absence of side effects ie:

  (let ((state 'someobject))
    (defun phase1 ()
      (done1 state))
    (defun phase2 ()
        (done2 state)
. . .

and so on


Sure; there's no silver bullet. My remark about closures was more to address his criticism that packaging up and passing around state was too much work to be worth it.

Programming in Lua has a nice example of a maze game where state is passed around by emitting functions. It's a toy problem, but I think it's instructive: http://www.lua.org/pil/6.3.html


An arcade game should be organized using a game loop. Just make the game loop update the state of the entire game world each frame. This makes it relatively easy to write most of the game in a functional fashion. (You may have to have n "phases" in your game loop. Movement phase, collision resolution phase, etc.)

The OP is accustomed to thinking only in terms of mutating state. He can't get away from it. He wouldn't have had all of these problems if he'd just done ye olde AI class with turn-based board game project in Lisp. (With a prof who set a coding standard for pure functional Lisp.)


This same guy also wrote an article describing the exact same functional style game loop that you are describing:

http://prog21.dadgum.com/23.html


I stand corrected. He knows his stuff already. I'll reread his OP.


How do you solve the problems he mentioned in his followup post? He seems to be saying that there are a lot of people going on about how easy it is but no examples beyond simple games.


i think pure functional programming is not a practical possibility. functional systems are functional until they are not (scheme's set!, clojure's transactions, ....) maybe the important thing is to keep in mind which parts of your system mutate data, and which parts do not.


Right -- like Haskell, for example. Mixed paradigm programming is surely the way to go; ideology is surely the wrong way. C and OCaml both support mixed programming, but Haskell supports it better: monads allow you to isolate the side-effecting portions of your program. It's funny that the OP mentions Backus' FP, which had a "job control" language that functioned like the IO monad.


or even adding an "n" in front of the function name goes a loooooooong (emphasis on the "o") way towards helping. nconc, nreverse ...


This is exactly the idea I had a while ago. I was talking with a friend and we were designing a language where you would have pure and impure functions. That way you can easily see what's going on and what to expect but you don't have to jump through a load of hoops when modifying a bit of state would have done the job.


check out chapter 3 of "on lisp". specially the part about functional interfaces.


Just had a skim, looks like the kind of thing I was thinking altough clearly he's thought it through a bit more. Thanks for the link, I'll take a look properly later.


Cue the apologists!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: