所有示例都使用 Ramda
作为 _
(很清楚示例上下文中的方法做什么)和 kefir
作为 frp
(与 bacon.js 中的 API 几乎相同)
我有一个流,它描述了位置的变化。
var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
当我按下
+1
键和 UP
上的 -1
键时,它会发出 DOWN
。为了获得位置,我
scan
这个增量var x = xDelta
.scan(_.add)
.toProperty(0);
这是预期的工作。但我想将
x
的值从 0
限制为 1000
。为了解决这个问题,我找到了两个解决方案:
scan
中的更改函数var x = xDelta.scan(function (prev, next) {
var newPosition = prev + next;
if (newPosition < 0 && next < 0) {
return prev;
}
if (newPosition > 1000 && next > 0) {
return prev;
}
return newPosition;
}, 0);
看起来还可以,但是后来,随着新规则的引入,这种方法会越来越多。所以我的意思是它看起来不是可组合的和 FRPy。
current
位置。和 delta
。我想将 delta
应用于 current
,前提是 current after applying
不会超出限制。current
取决于 delta
delta
取决于 current after applying
current after applying
取决于 current
所以它看起来像循环依赖。但我使用
flatMap
解决了它。var xDelta = frp
.merge([
up.map(_.multiply(1)),
down.map(_.multiply(-1))
])
.sampledBy(frp.interval(10, 0))
.filter();
var possibleNewPlace = xDelta
.flatMap(function (delta) {
return x
.take(1)
.map(_.add(delta));
});
var outOfLeftBoundFilter = possibleNewPlace
.map(_.lte(0))
.combine(xDelta.map(_.lte(0)), _.or);
var outOfRightBoundFilter = possibleNewPlace
.map(_.gte(1000))
.combine(xDelta.map(_.gte(0)), _.or);
var outOfBoundFilter = frp
.combine([
outOfLeftBoundFilter,
outOfRightBoundFilter
], _.and);
var x = xDelta
.filterBy(outOfBoundFilter)
.scan(_.add)
.toProperty(0);
你可以在 iofjuupasli/capture-the-sheep-frp 看到完整的代码示例
它正在运行演示 gh-pages
它有效,但使用循环依赖可能是反模式。
有没有更好的方法来解决 FRP 中的循环依赖?
第二个更普遍的问题
使用
Controller
可以从两个 Model
中读取一些值,并根据它的值更新它们。所以依赖看起来像:
---> Model
Controller ---|
---> Model
FRP 没有
Controller
。所以 Model
值应该从其他 Model
声明性地计算出来。但是如果 Model1
从另一个相同的 Model2
计算,那么 Model2
从 Model1
计算呢?Model ----->
<----- Model
例如有碰撞检测的两个玩家:两个玩家都有
position
和 movement
。第一个玩家的 movement
取决于第二个玩家的 position
,反之亦然。我仍然是所有这些东西的新手。经过多年的命令式编码,以声明式 FRP 风格开始思考并不容易。可能我错过了一些东西。
最佳答案
是和否。从您在实现这一点时遇到的困难中,您可以看到很难创建循环依赖项。尤其是声明式的。但是,如果我们想使用纯声明式风格,我们可以看到 循环依赖是无效的 。例如。在 Haskell 中,您可以声明 let x = x + 1
- 但它会评估为异常。
如果你仔细看,它不会。如果这是一个真正的循环依赖,那么 current
就没有任何值(value)。或 threw an exception 。
相反, current
确实 取决于其先前的状态 。这是 FRP 中众所周知的模式,即 步进器 。取自 this answer :
e = ((+) <$> b) <@> einput
b = stepper 0 e
在不知道
<$>
和 <@>
究竟做了什么的情况下,您可能会知道事件 e
和行为(“属性”) b
如何依赖于事件 einput
。更好的是,我们可以声明性地扩展它们:e = ((+) <$> bound) <@> einput
bound = (min 0) <$> (max 1000) <$> b
b = stepper 0 e
这基本上就是 Bacon 在
scan
中所做的。不幸的是,它迫使您在单个回调函数中完成所有这些。我还没有在任何 JS FRP 库中看到
stepper
函数1。在 Bacon 和 Kefir 中,如果要实现此模式,可能必须使用 Bus
。我很高兴被证明是错误的:-)[1]:嗯,除了因为这个原因我自己实现的那个(它还不能展示)。但是使用
Stepper
仍然感觉像是跳过了箍,因为 JavaScript 不支持递归声明。关于javascript - FRP中EventStreams的循环依赖,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29478305/