所有示例都使用 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 计算,那么 Model2Model1 计算呢?
    Model ----->
          <----- Model
    

    例如有碰撞检测的两个玩家:两个玩家都有 positionmovement 。第一个玩家的 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/

    10-11 12:37