23

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

如果我们谈论稍微复杂一点的游戏,比如 Space Invaders,我们有很多状态(外星人的位置、外星人当前的 HP、剩余的炸弹数量等)

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

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

4

5 回答 5

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}

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

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

于 2010-07-28T10:44:41.610 回答
6

我对 F# 下的响应式编程没有任何经验,但是纯函数系统中的全局状态问题很常见,并且有一个非常优雅的解决方案:Monads

虽然 monad 本身主要在 Haskell 中使用,但其基本概念已作为计算表达式进入 F# 。

这个想法是你实际上并没有改变状态,而只是描述状态的转换,即如何产生新的状态。状态本身可以完全隐藏在程序中。通过使用特殊的 monadic 语法,您几乎可以命令式地编写纯粹但有状态的程序。

从此源获取(修改的)实现,Statemonad 可能看起来像这样

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

尽管如此,这里的一切都是纯粹的功能;)

于 2010-07-28T07:49:40.780 回答
3

Tomas 就F# 中的反应式编程进行了精彩的演讲。许多概念应该适用于您的情况。

于 2010-07-28T09:55:30.867 回答
0

Maybe you will want to have a look at FsReactive.

于 2011-05-10T18:30:09.113 回答
0

Elm是一种现代 FRP 实现。对于在 Space Invaders 等游戏中普遍存在的动态集合建模,它包含一个基于箭头化 FRP 概念的Automaton 库。你一定要检查一下。

于 2013-01-09T18:32:13.107 回答