4

所有示例都使用Ramdaas _(很清楚示例上下文中的方法)和kefiras frp(与 bacon.js 中几乎相同的 API)

我有一个流,它描述了位置的变化。

var xDelta = frp
    .merge([
        up.map(_.multiply(1)),
        down.map(_.multiply(-1))
    ])
    .sampledBy(frp.interval(10, 0))
    .filter();

+1当我按下UP键和-1on时它会发出DOWN

为了获得scan这个增量的位置

var x = xDelta
    .scan(_.add)
    .toProperty(0);

这是预期的工作。但我想限制xfrom 0to的值1000

为了解决这个问题,我找到了两个解决方案:

  1. 更改功能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。

  1. 我有current立场。和delta。我想申请deltacurrent只要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. 所以Modelvalue 应该从 other 以声明方式计算Model。但是如果Model1从另一个Model2相同的计算,那么Model2计算从Model1

Model ----->
      <----- Model

例如两个具有碰撞检测的玩家:两个玩家都有positionmovementmovement第一个玩家取决于第二个玩家,position反之亦然。

我对所有这些东西还是新手。经过多年的命令式编码,开始以声明式 FRP 风格思考并不容易。可能我错过了一些东西。

4

2 回答 2

4

使用循环依赖可能是反模式

是和不是。从您在实现这一点时遇到的困难中,您可以看到很难创建循环依赖。尤其是以声明的方式。但是,如果我们想使用纯声明式的风格,我们可以看到循环依赖是无效的。例如,在 Haskell 中,您可以声明let x = x + 1- 但它会评估为异常。

current取决于delta,delta取决于current after applying, current after applying取决于current

如果你仔细观察,它不会。如果这是一个真正的循环依赖,那么current它就没有任何价值。或者抛出异常

相反,current确实取决于其先前的状态。这是 FRP 中众所周知的模式,即步进器。取自这个答案

e = ((+) <$> b) <@> einput
b = stepper 0 e

在不知道具体做什么<$><@>具体做什么的情况下,您可能可以知道事件e和行为(“属性”)如何b依赖于事件einput。更好的是,我们可以声明式地扩展它们:

e = ((+) <$> bound) <@> einput
bound = (min 0) <$> (max 1000) <$> b
b = stepper 0 e

这基本上是培根所做的scan。不幸的是,它迫使您在一个回调函数中完成所有这些工作。

我没有stepper在任何 JS FRP 库1中看到函数。Bus在 Bacon 和 Kefir 中,如果要实现此模式,您可能必须使用 a 。我很高兴被证明是错误的:-)

[1]:嗯,除了我自己实现的那个(它还不能像样)。但是使用Stepper仍然感觉像是在跳槽,因为 JavaScript 不支持递归声明。

于 2015-04-06T20:36:57.940 回答
1

有一个名为 cyclejs的新框架/库可以完全按照您描述的循环机制工作,但在这种情况下,是一个类似于 Facebook 的新 React 的 webfrontend 库。

基本思想是有一个模型,它是一个“状态”值流,一个呈现这些值的视图流,一个用户交互流,它发出来自视图(浏览器 DOM)的副作用的用户交互和一个“意图”流,它从用户创建高级事件并馈送到创建新值的模型中。

它仍处于早期开发阶段,但这是一个非常巧妙的想法,到目前为止运行良好。

于 2015-04-09T20:36:37.800 回答