

Admitting that Functional Programming Can Be Awkward - iamelgringo
http://prog21.dadgum.com/3.html?

======
ajuc
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.

~~~
ido
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.

~~~
utx00
i thought it sounded pretty clever

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

~~~
swannodette
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.

------
silentbicycle
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>

~~~
miloshh
_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.

~~~
silentbicycle
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.

~~~
miloshh
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.

~~~
AlisdairO
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.

------
radu_floricica
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?

------
jrockway
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.)

~~~
psranga
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.

~~~
jrockway
_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.)

~~~
teamonkey
"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.

~~~
jrockway
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.

~~~
teamonkey
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.

------
joeyo
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!

~~~
utx00
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

~~~
joeyo
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>

------
stcredzero
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.)

~~~
copenja
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>

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

------
utx00
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.

~~~
mgreenbe
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.

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

------
barryfandango
Cue the apologists!

