功能反应F#-在游戏中存储状态
我是一名学生,目前正在使用F#学习功能反应范式。这对我来说是全新的观点。昨天我学习了如何使用这个范例创建一个简单的乒乓球游戏。到目前为止,我所理解的想法是:我们认为价值是时间的函数。就其纯形式而言,它是无状态的。但是,我需要记住球的位置(或状态)。所以我总是传递球的当前位置作为全局函数的参数 如果我们谈论稍微复杂一些的游戏,比如《太空入侵者》,我们有很多状态(外星人的位置、外星人当前的生命、剩余炸弹的数量等等) 有没有解决这个问题的优雅/最佳方法?我们总是在顶层存储状态吗?是否所有当前状态都应作为全局函数的附加输入参数 有人能用F#上的简单例子来解释这一点吗?功能反应F#-在游戏中存储状态,f#,functional-programming,state,frp,F#,Functional Programming,State,Frp,我是一名学生,目前正在使用F#学习功能反应范式。这对我来说是全新的观点。昨天我学习了如何使用这个范例创建一个简单的乒乓球游戏。到目前为止,我所理解的想法是:我们认为价值是时间的函数。就其纯形式而言,它是无状态的。但是,我需要记住球的位置(或状态)。所以我总是传递球的当前位置作为全局函数的参数 如果我们谈论稍微复杂一些的游戏,比如《太空入侵者》,我们有很多状态(外星人的位置、外星人当前的生命、剩余炸弹的数量等等) 有没有解决这个问题的优雅/最佳方法?我们总是在顶层存储状态吗?是否所有当前状态都应作
非常感谢。我对F#下的反应式编程没有任何经验,但是纯函数系统中的全局状态问题非常常见,并且有一个非常优雅的解决方案:Monads 虽然单子本身主要用于Haskell,但其基本概念使其成为F#as 这样做的目的是,您实际上不会更改状态,而只是描述状态的转换,即如何生成新状态。状态本身可以完全隐藏在程序中。通过使用特殊的一元语法,您几乎可以强制编写纯但有状态的程序 从中获取一个(修改过的)实现,
状态
monad可能如下所示
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
让我们举个例子:我们想编写一个函数,在继续操作时可以将值写入日志(只是一个列表)。因此,我们定义
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
}
在state
中,我们现在可以在命令式语法中使用它,而无需手动处理日志列表
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
不过,这里的一切都是纯功能性的;) 托马斯在F#中给出了一个关于反应式编程的例子。许多概念应该适用于您的案例。进行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}
然后,alienPos,alienHP的行为可以定义为依赖于玩家,playerPos,playerBombs的行为可以定义为依赖于外星人
无论如何,如果你能提供更多关于你正在使用哪种玻璃钢的细节,那么就更容易给出更具体的建议。(如果你想要什么样的建议-我个人建议阅读:)也许你会想看看。是一种现代FRP实现。对于在游戏(如《太空入侵者》)中无处不在的动态集合建模,它包含基于箭头化FRP概念的。你一定要去看看。这主要是关于状态单子的。函数式反应式编程通常涉及单子,但通常不涉及这种简单的状态单子。正如我所说,我没有FRP方面的经验。尽管如此,状态monad(或者完全是monad)似乎是人们要求的概念——方便地存储和修改上下文数据,而不会失去引用的透明度。如果FTP已经使用了一元基础设施,那就更好了。一个状态单变量转换器应该可以实现这一点(你是说简单类型的吗?)。但如果没有解释基本原理,这些信息将毫无用处!FRP的要点是允许将行为定义为时间的连续函数-例如,可以将球在重力作用下的z位置定义为z(t)=9.8*t*t。一元状态仅与进行离散更改的状态相关-FRP中也允许离散更改,但它们不太集中,并且通常不符合单子的确切形式。@RD1:很有趣,谢谢。但是,大多数与用户输入相关联的操作不是本质上是离散的吗?即使没有-为一个球定义一个连续的世界函数很简单,但如果系统稍微复杂一些(更多的球),这不都归结为求解(即积分)微分方程吗?鼠标的实际位置是一个连续变量,在FRP中是这样处理的。当然,可用的位置数据仅采样并近似于实际位置,但无论如何,这在FRP中到处都会发生,如果鼠标位置用于控制屏幕上或游戏中某物的位置,则将其视为时间的连续函数是非常自然的。微分方程在某些方法中是相关的,但不是必要的。我不是专家,请看:函数式反应式编程不仅仅是函数式语言中的反应式编程。主要技术是将行为表示为时间的函数。这些行为可能相互依赖,也可能取决于事件