1

I'm fairly new to reactive programming, and I'm starting to play with kefirjs. On the face of it, it looks like bacon/kefir's event streams and properties are essentially a way to express a dependency/computation dag that varies with time, and there are neat ways to hook up these dags to DOM events and UIs. Ok, sounds good.

I know that in general, when you have a computation dag, if you change the value of a single upstream node and naively push the computation downstream recursively, the well-known problems are:

  1. it takes up to exponential time relative to the number of dag nodes affected

  2. intermediate computations use a mix of the previous and current values of the input node, resulting in strange/inconsistent transient output values.

I see from playing around with some simple toy programs that kefir does such updates naively, so (1.) and (2.) do in fact occur. (I haven't tried bacon.)

Are these not considered to be problems?

For an example, in the following program, I'd expect the output x6 to change directly from 64 to 640, at a cost of 12 combinator evaluations (6 initially, and 6 triggered by the single update). Instead, the output goes 64,73,82,91,...,622,631,640 (63 pointless intermediate values) at a cost of 132 combinator evaluations (6 initially, which makes sense, and then 126 for the update, which is excessive).

https://codepen.io/donhatch/pen/EXYqBM/?editors=0010

// Basic plus
//let plus = (a,b)=>a+b;

// More chatty plus
let count = 0;
let plus = (a,b)=>{console.log(a+"+"+b+"="+(a+b)+"  count="+(++count)); return a+b;};

let x0 = Kefir.sequentially(100, [1,10]).toProperty();
let x1 = Kefir.combine([x0,x0], plus).toProperty();
let x2 = Kefir.combine([x1,x1], plus).toProperty();
let x3 = Kefir.combine([x2,x2], plus).toProperty();
let x4 = Kefir.combine([x3,x3], plus).toProperty();
let x5 = Kefir.combine([x4,x4], plus).toProperty();
let x6 = Kefir.combine([x5,x5], plus).toProperty();
x6.log('                x6');

I'm rather mystified that kefir apparently makes no attempt to do such updates efficiently; I'd think that would make it a non-starter for real apps.

Maybe I'm misunderstanding what reactive programming is used for. Are the dags encountered in real-life reactive apps always trees, or what?

Don Hatch
  • 5,041
  • 3
  • 31
  • 48
  • Your sample above ported to Baconjs results in 12 evaluations: https://codepen.io/BlessYAHU/pen/VWwwbX?editors=0010 This may be worth posting this question to the Kefir repository, as it might be an optimization opportunity. – Bless Yahu Jun 02 '17 at 15:42
  • 1
    Real life dags are often very complex and not necessarily trees. This makes it hard to reason about possible unwanted intermediate values, that I call glitches. And this is why in Bacon.js (but not Kefir) there's this thing called Atomic Updates. – raimohanska Jun 12 '17 at 08:00

1 Answers1

2

Bacon.js has Atomic Updates (https://github.com/baconjs/bacon.js/#atomic-updates) while Kefir has not. This is why with Bacon.js you'll see your desired behaviour while with Kefir you'll get a lot of intermediate values.

raimohanska
  • 3,265
  • 17
  • 28
  • The downside of this mechanism is of course that it's rather complicated and bad for performance, as it requires keeping track of pending updates in the dependency tree of the observables. – raimohanska Jun 12 '17 at 07:56