功能反应F# - 在游戏中存储状态

use*_*836 23 f# state functional-programming frp

我是一名正在学习使用F#的功能反应范式的学生.这对我来说是一个全新的观点.昨天我学会了使用这个范例创建一个简单的乒乓球比赛.我到目前为止所掌握的想法是:我们认为价值观是时间的函数.纯粹的形式,它是无国籍的.但是,我需要记住球(或状态)的位置.所以我总是将球的当前位置作为全局函数的参数传递.

如果我们谈论轻微的更复杂的游戏,比如太空入侵者,我们有很多州(外星人的位置,外星人当前的HP,剩余的炸弹数量等)

是否有一种优雅/最好的方法来解决这个问题?我们总是将州存储在最高层吗?是否应将所有当前状态作为全局函数的附加输入参数?

任何人都可以使用F#上的简单示例来解释这一点吗?非常感谢.

RD1*_*RD1 13

FRP的方法不止一种,而且它是一个活跃的研究领域.什么是最好的可以很大程度上取决于事物如何相互作用的细节,未来可能会出现新的和更好的技术.

从广义上讲,这个想法是将行为作为时间函数代替普通价值(如你所说).行为可以根据其他行为来定义,并且可以定义为在特定事件发生时在其他行为之间进行交换.

在你的例子中,你通常不需要通过参数记住球的位置(但对于某些类型的FRP你可能会这样做).相反,您可以只有一个行为:
ballPos : time -> (float * float)
这可能具有全局范围,或者对于更大的程序,在该范围内拥有一个本地范围并使用它可能更好.

随着事情变得越来越复杂,您将以越来越复杂的方式定义行为,依赖于其他行为和事件 - 包括在不同FRP框架中以不同方式处理的递归依赖.在F#中,对于递归依赖,我希望你需要let rec包括所有涉及的行为.这些仍然可以组织成结构 - 在顶级你可能有:

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}
Run Code Online (Sandbox Code Playgroud)

然后可以定义alienPos,alienHP的行为,依赖于玩家,而playerPos,playerBombs可以定义为依赖外星人.

无论如何,如果你能提供更多关于你正在使用什么样的FRP的细节,那么提供更具体的建议会更容易.(如果你想要什么样的建议 - 我个人推荐阅读:http://conal.net/papers/push-pull-frp/push-pull-frp.pdf)


Dar*_*rio 6

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
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

不过,这里的一切都是纯粹的功能;)