4

All examples uses Ramda as _ (it's clear what methods do in examples contexts) and kefir as frp (almost same API as in bacon.js)

I have a stream, that describes change of position.

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

It emits +1 when I press UP key, and -1 on DOWN.

To get position I scan this delta

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

That's work as expected. But I want to limit value of x from 0 to 1000.

To solve this problem I found two solution:

  1. Change function in 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);
    

It looks Ok, but later, as new rules will be introduced, this method will grow. So I mean it doesn't look composable and FRPy.

  1. I have current position. And delta. I want to apply delta to current, only if current after applying will not be out of limits.

    • current depends on delta
    • delta depends on current after applying
    • current after applying depends on current

    So it looks like circular dependency. But I solved it using 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);
    

    You can see full code example at iofjuupasli/capture-the-sheep-frp

    And it's working demo gh-pages

    It works, but using circular dependencies is probably anti-pattern.

Is there a better way to solve circular dependency in FRP?

The second more general question

With Controller it's possible to read some values from two Model and depending on it's values update both of them.

So dependencies looks like:

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

With FRP there is no Controller. So Model value should be declaratively calculated from other Model. But what if Model1 calculating from another Model2 which is the same, so Model2 calculates from Model1?

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

For example two players with collision detection: both players have position and movement. And movement of first player depends on position of second, and vice versa.

I'm still newbie in all this stuff. It's not easy to start think in declarative FRP style after years of imperative coding. Probably I'm missing something.

Bergi
  • 513,640
  • 108
  • 821
  • 1,164
iofjuupasli
  • 3,509
  • 1
  • 14
  • 17
  • Please ask one question per post only. You may [edit] the second out and [ask it as an extra question](http://stackoverflow.com/questions/ask) – Bergi Apr 06 '15 at 20:01
  • I think it still one question. I wrote _second_, but entirely the same. When I ask question on SO, I want to get general solution of problem. Not only piece of code that solve problem. But also I want to show real example. So you can get what I mean exactly – iofjuupasli Apr 06 '15 at 20:03
  • Notice you can shorten your `scan` solution a lot like [here](http://stackoverflow.com/a/25755608/1048572) – Bergi Apr 06 '15 at 20:07

2 Answers2

4

using circular dependencies is probably anti-pattern

Yes and no. From the difficulties you had with implementing this, you can see that it's hard to create a circular dependency. Especially in a declarative way. However, if we want to use pure declarative style, we can see that circular dependencies are invalid. E.g. in Haskell you can declare let x = x + 1 - but it will evaluate to an exception.

current depends on delta, delta depends on current after applying, current after applying depends on current

If you look closely, it doesn't. If this were a true circular dependency, current never had any value. Or threw an exception.

Instead, current does depend on its previous state. This is a well-known pattern in FRP, the stepper. Taking from this answer:

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

Without knowing what <$> and <@> exactly do, you can probably tell how the events e and the behaviour ("property") b depend on the events einput. And much better, we can declaratively extend them:

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

This is basically what Bacon does in scan. Unfortunately it forces you to do all of this in a single callback function.

I haven't seen a stepper function in any JS FRP library1. In Bacon and Kefir, you'll probably have to use a Bus if you want to implement this pattern. I'd be happy to be proven wrong :-)

[1]: Well, except in the one I have implemented myself because of this (it's not presentable yet). But using Stepper still felt like jumping through hoops, as JavaScript doesn't support recursive declarations.

Community
  • 1
  • 1
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • `depend on its previous state` here is my mistake. And it answer for the general question. So `ModelOne` depends on `*Previous`, but `*Previous` can't depends on future values. So there is no circular dependencies. From [what is FRP](http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming) **Dynamic/evolving values (i.e., values "over time") are first class values in themselves.**. So `value` in `time` and `value` in `time+1` is different values. As I understand it. Correct me if I'm wrong. – iofjuupasli Apr 06 '15 at 20:51
  • Yes, exactly. And the program tries to evaluate `value(time+∞)`, starting with `value(time)`. This doesn't work exactly well with JS' async model of time, tough. – Bergi Apr 06 '15 at 20:55
1

There is a new framework/library called cyclejs that works off exactly the circular mechanism you describe, but in that case for a webfrontend library similar to Facebook's new React.

The basic idea is to have a Model that is a stream of "state" values, a view stream that renders those, a user-interaction stream that emits the user interactions coming from the sideeffect of the view (the browser DOM) and an "intent" stream that creates high level events from the user and feeds into the model which creates new values.

It's still in early development, but it's a pretty neat idea and works well so far.

Daniel Bachler
  • 141
  • 1
  • 5