Reconsidering Functional Programming
prog21.dadgum.com
prog21.dadgum.com
> single-assignment "variables,"
It's a pet peeve of mine to see people disparage the phrase "immutable variable" as a contradiction. A given variable's value can vary each time its function is called, or each time its program is run, without ever needing to be mutated. In fact, in practice most variables don't end up requiring mutation, which was a surprise for me coming from traditional imperative languages (not to say that mutation isn't valuable, but it makes more sense for your language to make variables immutable by default rather than mutable by default). </rant>Variable is just a name for value.
"If a tree falls in the woods, does it make a sound? If a pure function mutates some local data in order to produce an immutable return value, is that ok?"
Function purity enforces that mutations are localized to small areas of the code, while maintaining composition.
"let" keyword in ES6 allows Javascript to have "lexical" style scoping (with some caveats due to JS legacy).
That said, several ways of scoping a variable can be had in functional programming. Have a look here for some more explanation with regards to Common Lisp:
http://stackoverflow.com/questions/463463/dynamic-and-lexica...
Most programmers of 'traditional' (imperative, oo) programming languages would agree.
For a couple of LudumDares I did try (in java) and for the games I was able to do it worked fairly well. Basically the drawing thread is seeing a frozen readonly version of the world (and keeps 60 fps) while the update thread will try to run simlation over readwrite version of the world. When the update thread finishes it will "publish" the new state and start over. I have ever wondered how it will perform at scale, maybe it will start braking apart if it grows too much?
In particular you have to organize all your world data in flat mutable arrays because even on the CPU your data has to be presented in a way that avoids cache misses as much as possible, this philosophy goes by "data-oriented design", see for example https://www.youtube.com/watch?v=rX0ItVEVjHc.
Not really, it's a pretty trivial problem and one of the least concerns of game programming these days.
If your actors has a single 'update' method, the first actor would check for collisions and move before any other actors had a chance to do anything. This gives certain actors preferential treatment in movement.
Of course, you could instead plan out the dependencies better; do all collision detection before all physics response. But this is just a different way to look at 'immutability'
There may be exceptions to this; it is not a subject I keep current with.
eg: instead of:
foreach actor(find collisions -> find response -> do movement)
do: foreach actor(find collisions) ->
foreach actor(find response) ->
foreach actor(do movement)
That way you may modify the primary data structure directly in the 'do movement' response without any conceptual 'data races'. It really just off loads the information lost from 'mutability' to the secondary data structures of collision lists and the like.There are a lot of problems with global state in games, but entity values are not one of them, mostly. Problems do crop up but you just deal when they do.
In Starcraft II, when 2 Protoss players order one of their templars to move/cast a particular fatal spell to the other templar, then wait, you do not get the expected double kill. Instead, when they get in range of each other one templar casts the spell first, kill the other, and survives.
Which templar lives and which dies depends on the order the units are processed in the frame —arguably an implementation detail. To the players, this is just plain unfair. Not a deal breaker for a hosts of reasons, but still no good.
It's not game breaking, but players noticed this.
To avoid such problem with marines shooting (this would be game-breaking, because marines make up bulk of terran armies for most of the game) - there is miniscule random factor added to delay between shots for each marine.
If it was important enough - they would fix the high templar/ghost problem the same way.
BTW in e-sport games (like Starcraft) - finding such bugs and abusing them creatively is part of game. For example mineral-walking is legitimate techinque to micromanage your workers, used even in low-level matches, and it's caused by workaround of bug in Starcraft 1.
- Unit1 kills unit2
- Unit2 kills unit1
If you double buffer your actions properly, both unit will be dead at frame N+1. If however you modify your data in place, this may happen: 1. Unit1 kills unit2 => delete unit 2
2. Unit2 no longer exists. Unit1 lives.
Or, this may happen: 1. Unit2 kills unit1 => delete unit1
2. Unit1 no longer exists. Oops.
Oops.What definitely does not happen is the double kill. StarcraftII can live with that, but other games, like Frozen Synapse, would be broken.
In that case you take all the attacks, computes the damages, apply them and prune the dead.
More generally this is pretty much the event sourcing pattern: http://martinfowler.com/eaaDev/EventSourcing.html
Event recording has a fair bit of history in games, especially as a debugging technique, but I did not want to use it for rewind, considering it too fragile and annoying, and probably too expensive and complicated (you would have had to store world state anyway, to have something nearby to delta from so that you don't start from the beginning of time every frame, so now you have TWO systems: world state recording and event recording. Better to stick with one.)
Also, big fan! Great work on the new language/compiler livecasts and can't wait for The Witness.
It is more expensive in terms of the amount of memory required, but it is much less expensive in terms of the amount of CPU required, and CPU was ultimately the biggest problem, so it seems I made the right decisions. Even on a limited-memory console like the Xbox 360 you can rewind most levels for 30-45 minutes before running out of buffer. That is more than anyone ever wants to do as a practical gameplay interaction.
Working on The Witness... it will be done ... someday not too long from now.
Every player plans their movements for the next fraction of a second and all turns get executed simultaneously.
In world 4, though, where you can walk to the beginning of time just by walking to the leftmost part of the level, I actually kick the player out of the level when there's no more memory. I have never heard of anyone noticing this.
Game creator Jonathan Blow explains the rewind in a video (27min): http://www.youtube.com/watch?v=tSeYShR-OG0
And there is Gamasutra article about Rewind, in four parts: http://gamasutra.com/blogs/CameronLeBlanc/20130220/187036/Re... and part 2-3: http://www.gamasutra.com/blogs/CameronLeBlanc/20130313/18844...
EDIT: to be clear, the GP of your comment is jblow, not the parent.
http://research.microsoft.com/en-us/people/smcdirm/managedti...
If you modify the past, events are recorded and replayed, however (just rewinding focuses on a different world state). This is all baked in directly to the programming model. Another thing we are able to do is capture deltas rather than do entire world checkpoints, which works out well since many states do not change on all time steps (except for that moving bullet, of course). So each cell has multiple entries of temporally scoped values (for values that change on every frame, they just have one for each frame).
Also, sweet game! I actually just introduced it to my gf last week.
https://78462f86-a-feb80ac8-s-sites.googlegroups.com/a/tetsu...
It is not very practical, however. Many operations that you'd want to do are not time reversible without saving things.
http://www.math.ucsd.edu/~sbuss/CourseWeb/Math268_2013W/Benn...
So as a bluntly simple example, assume the entire world's state is just the number 100. Instead of storing 105 as the new state, you store {:increment, 5}, and somewhere, the reversible counterpart to increment is defined {:decrement, 5}. You're correct that a full global state would have to be stored at SOME point, but could you simply hold the initial state, and the current state, and work from either of those?
Or just do what the mp4 video algorithm does and store so-called "keyframes" every N frames which store the entire state, but all intermediary frames are just diff frames.
Many web apps end up needing a global event feed/stream that various processes hook into via publish/subscribe. In theory, these events are reversible. Of course, in practice, individual types of events may find difficulty, especially if they have side effects that are outside the control of the app developer
The Monad gives you purely functional sideeffects.
And changing topic, I am not a huge fan of how the Writer forces your code into a monadic style. One of the things I enjoy the most when I program in Ocaml instead of Haskell is being able to put printfs or mutable buffers wherever I want.
Then I don't understand your earlier comment.
"But even then, I kind of like being able to append values to a mutable buffer that is in scope without having to rewrite everything to use monads."
We can agree to disagree, there.
This is not to say that I don't value extra static safety. I think there is definitely room for languages that track effects without the syntactic weight of monads and monad transformers.
What I did not understand was your response of "the article wasn't talking about actually printing" to the suggestion of using Writer, despite your knowing that Writer is not about actually printing, Writer being in fact a good fit for what was described in the article, and the poster having quoted "printing" in the first place.
FYI, for the first need:
http://hackage.haskell.org/package/base-4.8.0.0/docs/Debug-T...
If you start by making everything in your program 100% immutable and timeless, how do you introduce any kind of time-dependence? A nice clean answer is that there is a global clock, and when it ticks, everything changes.
Both Go and Rust encourage single-assignment programming, with ":=", "let", and automatic type inference for assignments. C++ has had "auto" for years. The trend is towards single-assignment as a programming style. Declaring named values without initializing them is so last-cen.
Single-assignment programming is a bit more powerful than a pure functional style; you can use a value more than once. It's the difference between a tree and a directed acyclic graph. If you have code that does something imperative, talking to the world outside the program, trying to hammer that into a pure functional form is like pounding a screw.
It's also nice to have named variables when reading a program.
f(g.h(f2(g2(x))))
provides little information about why someone was doing that. When those intermediate results have names, it's somewhat more clear.In any case, I'm puzzled... why do you think writing in a pure FP language has anything to do with not naming your variables?
BTW, I'd say the opposite of your initial claim is true: single-assignment is the subset of FP, not viceversa. There's way more to FP than immutability, and in fact there are FP languages which allow (controlled) mutation.
I.e velocity v, acceleration a are modeled as floats. To integrate from instance in time t0 to t1, v1 = v0 + (t1 - t0) * a.
Now, I can replace any of those arithmetic operators with other arithmetic operators and still the statement is algebraically correct. However, the calculation is probably wrong,e .g. v1 = v0 * (t1 / t0) - a is a such broken permutation.
Naming time at point 0 as t0 and at point 1 as t1 are helpful mnemonics that help here.
Sure, we can create data types (that contain floats) Acceleration, TimeBefore, TimeAfter, VelocityBefore and VelocityAfter and create function (since we are ignoring names) asdf->VelocityBefore->TimeBefore->TimeAfter->Acceleration->VelocityAfter but the rub is - the implementation of that function needs to do that floating point arithmetic at some point.
Wrapping it in an algebraically explicit container does nothing to help in the correctness of the computation in itself. The main benefit here is for the end user in form of explicit types. Here, the function signature proves the OP:s point - signatures can be enough to document a function. However, it does nothing to verify the implementation.
Floating point operations are inherently "dynamically typed" in the sense that from a semantic point of view only some operations make sense when forming equations but modeling all of these relations using a type hierarchy is really, really cumbersome - and after the type hierarchy is in place, the hierarchy itself cannot prove the equation is correct, it just sets it in stone. Maths in inherently dynamically typed and good names help write better equations. If anyone have counter examples to prove this notion wrong I would appreciate them.
A smart lint system with a vocabulary for physics programs could know by convention that a name t means time and 0 and 1 means index and that in context of time the greater index means a step ahead in time. (At least this is what 90% of humans would assume). So it could infer t1 > t0 and check that at runtime in some debug mode. Maybe there are valid cases where t1 < t0 but then this naming introduces under guarantee false human interpretations of the source. The lint system should model and check against the typical human interpretation of names.
This does not detect the above wrong computation but it could help find errors without writing a test for every function.
Sure, when we have well defined domain logic rules those can be enforced through typing. But I would claim it is really unusual to do greenfield development and start from a really well defined set of domain logic rules formalized as a type system. Usually one discovers those rules as one develops the system, and then encodes those rules in types.
The space of physically meaningful equations is vastly smaller than the space of all possible permutations of variables and operators.
Too bad only a few languages like F# come with dimensions attached to numbers out of the box :)
Note, however, that this "do not needlessly name things" does show up in actual programming. In standard Haskell practice, it's common to write very short, abstract variable names whenever the function is general, and in turn you're encouraged to write general functions whenever possible.
It lacks any facility for bindings because any binding is a storage of state. Roughly, since you can refer to every function and value literally, the idea behind unlambda is that you should.
(Note that by this way of looking at things, closures are not "functional" at all and are in fact embeddings of imperative mini-languages.) Historically, closures were more or less developed by accident as LISP codified its scoping rules; in that sense their presence isn't particularly indicative of "functional ness"; it's just that using them requires a GC and 40 years ago only functional languages had one.
Thanks for the link! I didn't know about Unlambda.
A couple of remarks though: by the author's own admission, Unlambda is not a purely functional language, when using the accepted terminology. The author then goes on to introduce what he acknowledges to be nonstandard terminology, so I'm not sure what this says about closures being functional or not under more mainstream definitions. Furthermore, Unlambda is a purposefully obfuscated language! Not sure how it is relevant for the purpose of my post or the OP's :)
Thanks anyway for the link, it's interesting.
Abstract type signatures induce eye glazing in most programmers. It induces my eyes to glaze over and I've actually passed a course in category theory. It's not like I don't understand type signatures: I just takes more time than reading a decent descriptive name and trusting my coworkers. If a function square doesn't actually square, but cubes, I have a coworker to corner.
By the way, I wouldn't use an argument "by laziness" to argue in favor of naming things. "Type signatures induce eye glazing in most programmers", and "it takes more time"? Well, most programmers also write most of the bugs :) Not sure what this says about the relative merits of naming vs types/structure...
In any case, this was a tangent. Since FP doesn't preclude naming things if you wish, I'm not sure what the OP was all about.
In any case, let ... is basically syntactic sugar for lambdas.
let x = a in b
is equivalent to (\x -> b)(a)
Other than aesthetics, the only reason to have a separate "let" syntax is to tell the typechecker that a function can be polymorphic.Go is probably the least "singe-assignment encouraging" of current languages. It does not provide any const/final mechanism for anything but compile-time constants, and does not allow the creation of immutable objects.
So no single-assignment style and no single-assignment without the style in Go. Not only is mutability not discouraged, there's even no way to turn it off for specific types/variables. Go is fully in the JS/Python/Ruby camp when it comes to mutability.
I wonder if keeping a tree of game states in persistent variable (like Clojure datastructures) could help with that.
We could keep the current state as a root of game states tree, AI code will calculate possible future states a few steps ahead (these future states would mostly share memory with parent and sibling states, and if we predict n frames forward AI can start in each frame from states in step t+n-1 and calculate their immediate kids only). This is trivialy parallelizable BTW.
And in each frame game engine would move the tree root to one of the kids depending on player action, culling the previous root and the branches not taken from the tree.
It is based on the observation that SSA is functional programming, and there is a well-defined process for transforming imperative code into SSA, so why not reverse the process and develop a functional language with imperative-looking, Python-like syntax?
The goal was to automatically extract parallelism from for-loops, internally converting them into map-reduce processes which could be spread across multiple cores, and that worked out pretty well. In absence of a marketing effort, however, a userbase never developed, and I haven't done any work on the project in a couple of years.
Lately though, as I've been weighing engine options for a CRPG project, I have become increasingly skeptical of its applicability or utility to games.
The simple point of the matter is that games are humongous state machines, but even more importantly, humongous state machines that are performance sensitive.
The standard approach so far has been to simply deal with this either monadically or atomically, having some big state object that gets passed through each cycle, copied again and again with every frame. This is all well and good when the full extent of your game state is a handful of blocks and a paddle, but stop and think how many, say, console-style RPGs you've seen done in functional languages to completion.
In the Pokemon games, there is a move called Defense Curl, that increases the users defense. Simple enough. Then in the second gen games, it was very silently given another effect: there is a particular attack move whose damage is doubled if the user has previously used Defense Curl in that fight. In the third gen, this was done again for an additional move that, at the time and for three more iterations of the series, was only available to a single character.
That is an incredibly specific state detail. And that's just one. The entire game is filled with little quirks and mechanical details like that. There are over 600 moves in the game, many with other unique effects, not to mention the 700+ creatures, almost 200 abilities, plus an entire hidden second stat system with the IVs.
Try passing a state object containing all the flags you need to keep track of that 30 or 60 frames a second.
Is it any wonder then that you see so little done with FP languages that isn't just clones of old arcade games? I know of one company working on an A-level game in Haskell. Beyond that, I've seen a couple Quake 3 ports and a whoooole lot of unfinished projects.
I'm just not convinced it's up to the task yet, and the research and theoretical end still seems to focus on trivial toy examples instead of full-scale projects that are on a whole other order of magnitude from Pac-Man. I seriously start to wonder how much of the learning so far and the 'obvious' patterns and solutions that seem to already just be accepted dogma, really have anything to do with real-world games programming in the large.
Maybe Wayward Tide will finally release source and prove me horribly wrong here, or Elm will indeed prove as promising as it looks when extended beyond simple browser toy experiments, but in the meantime I'm veering hard back to commercial, traditional engines instead.
Large games Will probably not go from C++ to Haskell, but instead (hopefully) from C++ to Rust, where the developer can choose a more elegant functional approach where possible, and will still have compiler guarantees about the parts that are mutable.
You can structure your data such that you only need to rewrite a portion of it, based on what changed. I couldn't more highly recommend "Purely Functional Data Structures" by Chris Okasaki.
With regards to the Pokemon example given, I can think of two good ways to encode it:
First, simply a bunch of single-bit flags, one per move per competitor. Each turn, for move tracking, we update at most one bit in one of these arrays by copying the array and updating a pointer. We can track 1000+ moves, and only need to copy 128 bytes. You can do thousands of comparable things and still be done in a single frame.
Second, a list of turn information. A move could ask arbitrary questions about the history of the current fight by walking the list. An append-only list trivially has an immutable sublist, and you only need to "pass" a single pointer. Walking the list should be trivially possible well inside a single frame, provided you don't have many thousands of turns in a single fight.
All that leaves aside the fact that you don't actually need to fit your world updates in a single frame render - you can interpolate/extrapolate. This is especially easy in a turn-based game like Pokemon, where it mostly consists of "smoothly progress through the same animation".
Hmm, have you tried it? I don't see an immediate problem. 60 Hz is not an immense rate for computers.
And state updates don't usually involve copying large structures, since pure languages use lots of sharing. It's more like updating a tree; you just do some logarithmic amount of consing.
There aren't that many companies using Haskell at all, so you can't infer much from the fact that there aren't many large-scale games written in it.
This is of course not an argument, but John Carmack doesn't seem to believe purity itself is an intractable performance problem!