Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

FP proponents have claimed that concurrency is easy because their paradigm avoids mutable state. I don't get it.

Imagine we're creating a multiplayer dungeon crawl (a roguelike) using FP where we emphasize pure functions and immutable data structures. We generate a dungeon composed of rooms, corridors, heroes, monsters and loot. Our world is effectively an object graph of structures and their relationships. As things change our representation of the world is amended to reflect those changes. Our hero kills a rat, picks up a shortsword, etc.

To me the world (current reality) carries this idea of state and I'm missing how FP overcomes this. As our hero takes action, functions amend the state of the world. It appears to be every decision (AI or human) needs to be based on the state of the world as it is in the present. Where would we allow for concurrency? We can't have multiple processes concurrently ammending the state of the world lest one process base its outcomes on some expired state. It feels to me that all control should occur within a single control loop so that we're always processing the present state represented by our current object graph of the world.

Clearly there are situations perfectly suited for concurrency (i.e. When processing isolated tasks whose states are independent of one another).

I'm failing to see how concurrency is useful in my example and that may be the issue. I may be misrepresenting the claim somehow.

Can someone better represent this claim?

share|improve this question
1  
You're referring to shared state; shared state will always be what it is and will always require some form of synchronization, the oft preferred form among pure FP people is STM which allows you to treat shared memory as local memory by having an abstraction layer over it that makes access transactional so race conditions are handled automatically. Another technique for shared memory is message passing where instead of having shared memory, you have local memory and knowledge of other actors to ask for their local memory –  Jimmy Hoffa Apr 22 '13 at 14:24
    
So... you're asking how shared-state concurrency being easy helps manage state in a single-threaded application? On the other hand, your example clearly lends itself to concurrency conceptually (a thread for each AI-controlled entity) whether or not it's implemented that way. I'm confused what you're asking here. –  C. A. McCann Apr 22 '13 at 14:54
1  
in a word, Zippers –  jk. Apr 22 '13 at 15:06
1  
Every object would have their own view of the world. There will be eventual consistency. It's probably also how things work in our "real world" with the wave function collapse. –  herzmeister Apr 22 '13 at 15:57
    
I suspected something along the lines of "eventual consistency" might be mentioned. In order to be eventually consistent, a concurrent processes after running its algorithms once over its copy of the "the world" will on the next pass grab the latest copy of the world? That is, are concurrent processes typically grabbing fresh copies or are they synchronizing their copies with the effective present state? –  Mario T. Lanza Apr 22 '13 at 16:11

4 Answers 4

I'll try to hint on the answer. This is not an answer, only an introductory illustration. @jk's answer points to the real thing, zippers.

Imagine you have an immutable tree structure. You want to alter one node by inserting a child. As a result, you get a whole new tree.

But most of the new tree is exactly the same as old tree. A clever implementation would reuse most of the tree fragments, routing pointers around the altered node:

From Wikipedia

Okasaki's book is full of examples like this.

So I suppose you could reasonably alter small parts of your game world each move (pick up a coin), and only actually change small parts of your world data structure (the cell where the coin was picked up). Parts that only belong to past states will be garbage-collected in time.

This probably takes some consideration in designing the data game world structure in an appropriate way. Unfortunately, I'm no expert in these matters. Definitely it must be something else than a NxM matrix one would use as a mutable data structure. Probably it should consist of smaller pieces (corridors? individual cells?) that point to each other, as tree nodes do.

share|improve this answer
2  
+1: For pointing at Okasaki's book. I haven't read it but it is on my to do list. I think what you depicted is the correct solution. As an alternative, you can consider uniqueness types (Clean, en.wikipedia.org/wiki/Uniqueness_type): using this kind of types you can destructively update data objects while retaining referential transparency. –  Giorgio Apr 22 '13 at 15:16
    
Is there a benefit for relationships to be defined via indirect reference via keys or ids? That is, I was thinking that fewer actual touches of one structure to another would necessitate fewer amendments to the world structure when a change occurs. Or is this technique not really used in FP? –  Mario T. Lanza Apr 22 '13 at 16:16

9000's answer is half the answer, persistent data structures allow you to reuse unchanged parts.

You may already be thinking however "hey what if I want to change the root of the tree?" as it stands with the example given that now means changing all the nodes. This is where Zippers come to the rescue. They allow element at a focus to be changed in O(1), and the focus can be moved anywhere in the structure.

The other point with zippers is that a Zipper exists for pretty much any data type you want

share|improve this answer
    
I'm afraid it will take me some time to dig into "zippers" since I'm on the fringe any only exploring FP. I have no experience with Haskell. –  Mario T. Lanza Apr 22 '13 at 16:14
    
I'll try to add an example later today –  jk. Apr 22 '13 at 16:21

Functional style programs create lots of opportunities like that to use concurrency. Anytime you transform or filter or aggregate a collection, and everything is pure or immutable, there's an opportunity for the operation to be sped up by concurrency.

For example, suppose you perform AI decisions independently of each other and in no particular order. They don't take turns, they all make a decision simultaneously and then the world advances. The code might look like this:

func MakeMonsterDecision curWorldState monster =
    ...
    ...
    return monsterDecision

func NextWorldState curWorldState =
    ...
    let monsterMakeDecisionForCurrentState = MakeMonsterDecision curWorldState
    let monsterDecisions = List.map monsterMakeDecisionForCurrentState activeMonsters
    ...
    return newWorldState

You have a function to compute what a monster will do given a world state, and apply it to every monster as part of computing the next world state. This is a natural thing to do in a functional language, and the compiler is free to perform the 'apply it to every monster' step in parallel.

In an imperative language you'd be more likely to iterate over every monster, applying their effects to the world. It's just easier to do it that way, because you don't want to deal with cloning or complicated aliasing. The compiler can't perform the monster computations in parallel in that case, because early monster decisions affect later monster decisions.

share|improve this answer
    
That helps quite a bit. I can see how in a game there would be great benefit to having monsters concurrently deciding what they'll do next. –  Mario T. Lanza Apr 21 at 17:07
up vote 1 down vote accepted

Listening to a few Rich Hickey talks -- this one in particular -- alleviated my confusion. In one he indicated that it is okay that concurrent processes may not have the most current state. I needed to hear that. What I was having trouble digesting was that programs would actually be okay with basing decisions on snapshots of the world that have since been superseded by newer ones. I kept wondering how concurrent FP got around the issue of basing decisions on old state.

In a banking application we would never want to base a decision on a snapshot of state that has since been superseded by a newer one (a withdrawal occurred).

That concurrency is easy because the FP paradigm avoids mutable state is a technical claim that doesn't attempt to say anything about the logical merits of basing decisions on potentially old state. FP still ultimately models state change. There's no getting around this.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.