FRP中EventStreams的循环依赖关系

时间:2015-04-06 19:35:13

标签: javascript circular-dependency frp bacon.js ramda.js

所有示例都使用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

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

  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);
    
  2. 它看起来很好,但后来,随着新规则的引入,这种方法将会增长。所以我的意思是它看起来不具有组合性和FRPy。

    1. 我有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

      它有效,但使用循环依赖可能是反模式。

    2. 有没有更好的方法来解决FRP中的循环依赖?

      第二个更一般的问题

      使用Controller,可以从两个Model中读取一些值,并根据它的值更新它们。

      所以依赖关系看起来像:

                    ---> Model
      Controller ---|
                    ---> Model
      

      使用FRP,没有Controller。因此Model值应该从其他Model以声明方式计算。但是,如果Model1从另一个Model2开始计算相同,那么Model2Model1开始计算会怎样?

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

      例如,两名有碰撞检测的玩家:两名玩家都有positionmovement。第一位玩家的movement取决于第二位的position,反之亦然。

      我仍然是所有这些东西的新手。经过多年的命令式编码后,开始以声明性的FRP风格思考起来并不容易。可能我错过了什么。

2 个答案:

答案 0 :(得分:4)

  

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

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

  

current取决于deltadelta取决于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)和“意图”流,用于从用户创建高级事件并将其提供给创建新值的模型。

它仍处于早期开发阶段,但它是一个非常巧妙的想法,并且目前运作良好。