views:

294

answers:

3

I am a student currently learning about Functional Reactive paradigm using F#. It's radically new viewpoint for me. Yesterday I learned about creating a simple ping-pong game using this paradigm. The idea I grasp so far is : we think values as functions of time. On its pure form, it's stateless. However, I need to remember the position of the ball (or state). So I always pass the current position of the ball as the parameter of the global function.

If we talk about slight more complex games, like Space Invaders, we have a lot of states (aliens' position, aliens' current HP, number of remaining bombs, etc)

Is there an elegant/best way to tackle this problem? Do we always store states on the top level? Do all current states should be given as the additional input argument of the global function?

Can anybody explain this using simple sample on F#? Thanks a lot.

+3  A: 

I haven't got any experience with reactive programming under F#, but the problem of global state in purely functional systems is quite common and has a quite elegant solution: Monads.

While monads themselves are primarily used in Haskell, the underlying concept made it into F# as computation expressions.

The idea is that you don't actually change states but just describe the transitions of the state, i.e. how to produce new states. The state itself can be completely hidden in the programm. By using special monadic syntax, you can write pure but stateful programms almost imperatively.

Taking a (modified) implementation from this source, the State monad could look like this

let (>>=) x f =
   (fun s0 ->
      let a,s = x s0    
      f a s)       
let returnS a = (fun s -> a, s)

type StateBuilder() =
  member m.Delay(f) = f()
  member m.Bind(x, f) = x >>= f
  member m.Return a = returnS a
  member m.ReturnFrom(f) = f

let state = new StateBuilder()     

let getState = (fun s -> s, s)
let setState s = (fun _ -> (),s) 

let runState m s = m s |> fst

So let's have an example: We want to write a function that can write values into a log (just a list) while proceeding. We therefore define

let writeLog x = state {
  let! oldLog = getState // Notice the ! for monadic computations (i.e. where the state is involved)
  do! setState (oldLog @ [x]) // Set new state
  return () // Just return (), we only update the state
}

Within state, we can now use this in an imperative syntax without having to handle the log list manually.

let test = state {
   let k = 42
   do! writeLog k // It's just that - no log list we had to handle explicitly
   let b = 2 * k
   do! writeLog b
   return "Blub"
}

let (result, finalState) = test [] // Run the stateful computation (starting with an empty list)
printfn "Result: %A\nState: %A" result finalState

Still, everything is purely functional here ;)

Dario
This is mostly about a state monad. Functional reactive programming often involves monads, but not usually this simple kind of state monad.
RD1
As I said, I haven't got experience in FRP. Nevertheless the state monad (or monads at all) seems to be the concept that has been asked for - storing and modifying context data conveniently without losing referential transparency. If FTP already uses a monadic infrastructure, so much the better. A State monad transformer should make the thing (do you mean that with *simple kind*?). But without having explained the fundamentals, this information would be useless!
Dario
The main point of FRP is allowing behaviours to be defined as continuous functions of time - e.g. you can define the z-position of a ball under gravity as z(t)=9.8*t*t . Monadic state is only relevant for state which makes discrete changes - discrete changes are also allowed in FRP, but they are less central and often they don't fit the exact form of a monad.
RD1
@RD1: Interesting, thanks. But aren't most actions linked to user-input *inherently discrete*? And even if not - defining a continuous world function for *one ball* is simple, but if the system is slightly more complex (more balls), doesn't this all come down to solving (i.e. integrating) differential equations?
Dario
@Dario: The actual position of a mouse is a continuous variable, and it's treated that way in FRP. Of course the available position data only samples and approximates the actual position, but this happens everywhere in FRP anyway, and it's very natural to treat the mouse position as a continuous function of time if it's being used to control the position of something on the screen or in a game. Differential equations are relevant in some approaches, but not essential. I'm no expert - see: http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming
RD1
+3  A: 

Tomas gave a nice talk about reactive programming in F#. Many concepts should apply in your case.

Mau
Functional reactive programming is quite a bit more than just reactive programming in a functional language. The main technique is representing behaviours as functions of time. These behaviours can depend on each other, and on events. So that talk isn't so directly relevant - it has some relevance, but only because events are one part of FRP. (I'd agree it's a nice talk though.)
RD1
+10  A: 

There's more than one way to do FRP, and it's an active area of research. What's best can depend a lot on the details of how things interact with each other, and new and better techniques may appear in the future.

Broadly the idea is to have behaviours that are functions of time in place of ordinary values (as you said). Behaviours can be defined in terms of other behaviours, and can be defined to swap between other behaviours when particular events occur.

In your example, you generally wouldn't need to remember the position of the ball via arguments (but for some kinds of FRP you might do). Instead you can just have a behaviour:
ballPos : time -> (float * float)
This might have global scope, or for a larger program it may be better to have a local scope with all uses of it in that scope.

As things get more complicated, you'll have behaviours defined in increasingly complex ways, depend on other behaviours and events - including recursive dependencies which are handled differently in different FRP frameworks. In F# for recursive dependencies I'd expect you'd need a let rec including all involved behaviours. These can still be organised into structures though - at the top-level you might have:

type alienInfo =  { pos : float*float; hp : float }
type playerInfo = { pos : float*float; bombs : int } 
let rec aliens : time -> alienInfo array =             // You might want laziness here.
    let behaviours = [| for n in 1..numAliens -> 
                        (alienPos player n, alienHP player n) |]
    fun t -> [| for (posBeh, hpBeh) in behaviours -> 
                {pos=posBeh t; hp=hpBeh t} |]          // You might want laziness here.
and player : time -> playerInfo  = fun t ->
    { pos=playerPos aliens t; bombs=playerBombs aliens t}

And then the behaviours for alienPos, alienHP can be defined, with dependencies on the player, and playerPos, playerBombs can be defined with dependencies on aliens.

Anyway, if you can give more details of what kind of FRP you're using, it will be easier to give more specific advice. (And if you want advice on what kind - personally I'd recommend reading: http://conal.net/papers/push-pull-frp/push-pull-frp.pdf)

RD1