我试图理解FRP图和带镜头的状态机之间的实用差异 - 特别是对于像游戏循环这样的事情,其中每个滴答都重新绘制整个状态。
使用javascript语法,以下实现基本上都可以工作:
选项1:带镜头的状态机
//Using Sanctuary and partial.lenses (or Ramda) primitives
//Each update takes the state, modifies it with a lens, and returns it
let state = initialValues;
eventSource.addEventListener(update, () => {
state = S.pipe([
updateCharacter,
updateBackground,
])
(state) //the first call has the initial settings
render(state);
});
选项2:FRP
//Using Sodium primitives
//It's possible this isn't the best way to structure it, feel free to advise
cCharacter = sUpdate.accum(initialCharacter, updateCharacter)
cBackground = sUpdate.accum(initialBackground, updateBackground)
cState = cCharacter.lift(cBackground, mergeGameObjects)
cState.listen(render)
我看到Option 1
允许任何更新在游戏状态的任何位置获取或设置数据,但Option 2
中的所有单元格/行为都可以调整为类型GameState
并且然后同样的事情适用。如果是这种情况,那么我对这种差异感到非常困惑,因为那样只会归结为:
cGameState = sUpdate
.accum(initialGameState, S.pipe(...updates))
.listen(render)
然后他们真的非常相同......
实现该目标的另一种方法是将所有Cell存储在某个全局引用中,然后任何其他单元可以对它们进行采样以供读取。可以传播新的更新以进行通信。该解决方案在一天结束时也与选项1非常相似。
在这种情况下,有没有办法构建FRP图,使其比事件驱动的状态机具有明显的优势?
答案 0 :(得分:2)
我不太确定你的问题是什么,也因为你不断改变解释性文字中的第二个例子。
在任何情况下,FRP方法的主要好处 - 正如我所看到的 - 如下:游戏状态取决于许多事情,但它们都明确列在定义的右侧cGameState
。
相反,在命令式样式中,您有一个全局变量state
,可能会也可能不会被您刚才提供的代码中未显示的代码更改。据我所知,下一行可能是
eventSource2.addEventListener(update, () => { state = state + 1; })
并且游戏状态突然取决于第二个事件来源,这一事实从您展示的片段中看不出来。这在FRP示例中不会发生:cGameState
的所有依赖关系在右侧都是明确的。 (当然,它们可能非常复杂,但至少它们是明确的。)