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.
Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter).
Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronousdataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter)
Forward Pricing Rate Proposal (FPRP) A Forward Pricing Rate Agreement (FPRA) is an agreement between a contractor and a government agency in which certain indirect rates are established for a specified period of time. These rates are estimates of costs and are used to price contracts and contract modifications.
Reactive programming describes a design paradigm that relies on asynchronous programming logic to handle real-time updates to otherwise static content. It provides an efficient means -- the use of automated data streams -- to handle data updates to content whenever a user makes an inquiry.
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)
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 ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With