所有示例都使用Ramda
作为_
(它清楚示例上下文中的方法)和kefir
作为frp
(几乎相同的API)如在bacon.js)
我有一个描述位置变化的流。
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风格思考起来并不容易。可能我错过了什么。
答案 0 :(得分:4)
使用循环依赖可能是反模式
是和否。从实现这一点的困难中,您可以看到很难创建循环依赖。特别是以声明的方式。但是,如果我们想使用纯声明式样式,我们可以看到循环依赖性无效。例如。在Haskell中,您可以声明let x = x + 1
- 但它会评估为异常。
current
取决于delta
,delta
取决于current after applying
,current after applying
取决于current
如果你仔细观察,它就不会。如果这是一个真正的循环依赖,current
从来没有任何价值。或threw an exception。
相反,current
依赖于之前的状态。这是FRP中众所周知的模式, stepper 。取自this answer:
e = ((+) <$> b) <@> einput
b = stepper 0 e
如果不知道<$>
和<@>
到底做了什么,您可以告诉事件e
和行为(“属性”)b
如何依赖于事件{ {1}}。更好的是,我们可以声明性地扩展它们:
einput
这基本上是培根在e = ((+) <$> bound) <@> einput
bound = (min 0) <$> (max 1000) <$> b
b = stepper 0 e
中所做的。不幸的是,它迫使你在一个回调函数中完成所有这些。
我没有在任何JS FRP库 1 中看到scan
函数。在Bacon和Kefir中,如果要实现此模式,则可能必须使用stepper
。 我很高兴被证明是错的: - )
[1]:嗯,除了我自己实现的那个(它还没有表现)。但是使用Bus
仍然感觉像是跳过了箍,因为JavaScript不支持递归声明。
答案 1 :(得分:1)
有一个名为cyclejs的新框架/库完全符合您描述的循环机制,但在这种情况下,类似于Facebook新的React的webfrontend库。
基本思想是拥有一个“状态”值流的模型,一个呈现这些值的视图流,一个发出用户交互的用户交互流来自视图的副作用(浏览器DOM)和“意图”流,用于从用户创建高级事件并将其提供给创建新值的模型。
它仍处于早期开发阶段,但它是一个非常巧妙的想法,并且目前运作良好。