VictorTaelin / PureState

The stupidest state management library that works.
MIT License
310 stars 18 forks source link

Question about the dependency detector system #7

Open synergistics opened 8 years ago

synergistics commented 8 years ago

Hi MaiaVictor!

I'm wondering about the dependency detector system and why it's necessary for your observable implementation. First off, could you elaborate a bit on how the system works, because I'm not understanding how you're able to know for example that if: var a = state(1) var b = state(() => a() + 1)

that a is a dependency of b. I'm sure that after looking over the code a little more it'll make sense.

Onto my next point, I'm not even sure that a detector is necessary at all. See this gist I made using a simpler model for observables. Without any hacks, the ternary example works correctly and the state function complies with the rules of PureState. Maybe I'm missing something, but I see this method as superior, for it's simpler, more transparent, and doesn't require any bookkeeping. Any thoughts?

Thanks, synergistics

VictorTaelin commented 8 years ago

Hello @synergistics, thanks for the question.

I'm able to know that a is a dependency of b because I do a shadow execution of b with a modified value of a. When that modified value of a is called, I detect that a is inside b. That is why branches screw this up, if a() is never executed it goes undetected.

The issue with your code is that you're duplicating work. Run this to understand:

var state = require("./purestate");

function state_(computation){
    var node = {};
    function wrapComp(computation){
        if (typeof computation !== "function"){
            return () => computation;
        } 
        else {
            return computation;
        }
    }

    node.computation = wrapComp(computation);

    function accessor(val){
        if (typeof val == "undefined"){
            return node.computation();
        }
        else {
            node.computation = wrapComp(val);
        }
    }

    return accessor;
}

[state, state_].map(function(state,j){
    console.log("Testing state  @"+(j?"without":"with")+" dependency graph.");

    var t = Date.now();
    var x = state(0);
    var y = state(() => {for (var i=0; i<400000000; ++i); return i});
    var z = state(() => [x(), y()]);
    for (var i=0; i<16; ++i){
        x(i);
        console.log("For i="+i
            +", [x,y,z] = "+JSON.stringify([x(), y(), z()])
            +" (computed in "+(Date.now()-t)/1000+"s)");
        t = Date.now();
    };
});

Running it outputs:

Testing state with dependency graph.
For i=0, [x,y,z] = [0,400000000,[0,400000000]] (computed in 0.551s)
For i=1, [x,y,z] = [1,400000000,[1,400000000]] (computed in 0s)
For i=2, [x,y,z] = [2,400000000,[2,400000000]] (computed in 0s)
For i=3, [x,y,z] = [3,400000000,[3,400000000]] (computed in 0s)
For i=4, [x,y,z] = [4,400000000,[4,400000000]] (computed in 0s)
For i=5, [x,y,z] = [5,400000000,[5,400000000]] (computed in 0s)
For i=6, [x,y,z] = [6,400000000,[6,400000000]] (computed in 0s)
For i=7, [x,y,z] = [7,400000000,[7,400000000]] (computed in 0s)
For i=8, [x,y,z] = [8,400000000,[8,400000000]] (computed in 0s)
For i=9, [x,y,z] = [9,400000000,[9,400000000]] (computed in 0s)
For i=10, [x,y,z] = [10,400000000,[10,400000000]] (computed in 0s)
For i=11, [x,y,z] = [11,400000000,[11,400000000]] (computed in 0s)
For i=12, [x,y,z] = [12,400000000,[12,400000000]] (computed in 0s)
For i=13, [x,y,z] = [13,400000000,[13,400000000]] (computed in 0s)
For i=14, [x,y,z] = [14,400000000,[14,400000000]] (computed in 0s)
For i=15, [x,y,z] = [15,400000000,[15,400000000]] (computed in 0s)
Testing state without dependency graph.
For i=0, [x,y,z] = [0,400000000,[0,400000000]] (computed in 0.577s)
For i=1, [x,y,z] = [1,400000000,[1,400000000]] (computed in 0.566s)
For i=2, [x,y,z] = [2,400000000,[2,400000000]] (computed in 0.56s)
For i=3, [x,y,z] = [3,400000000,[3,400000000]] (computed in 0.57s)
For i=4, [x,y,z] = [4,400000000,[4,400000000]] (computed in 0.533s)
For i=5, [x,y,z] = [5,400000000,[5,400000000]] (computed in 0.536s)
For i=6, [x,y,z] = [6,400000000,[6,400000000]] (computed in 0.549s)
For i=7, [x,y,z] = [7,400000000,[7,400000000]] (computed in 0.545s)
For i=8, [x,y,z] = [8,400000000,[8,400000000]] (computed in 0.548s)
For i=9, [x,y,z] = [9,400000000,[9,400000000]] (computed in 0.545s)
For i=10, [x,y,z] = [10,400000000,[10,400000000]] (computed in 0.569s)
For i=11, [x,y,z] = [11,400000000,[11,400000000]] (computed in 0.564s)
For i=12, [x,y,z] = [12,400000000,[12,400000000]] (computed in 0.544s)
For i=13, [x,y,z] = [13,400000000,[13,400000000]] (computed in 0.54s)
For i=14, [x,y,z] = [14,400000000,[14,400000000]] (computed in 0.543s)
For i=15, [x,y,z] = [15,400000000,[15,400000000]] (computed in 0.566s)

While the results are the same, your version without the dependency graph takes much more time to run because y() is called more than once, which isn't necessary.