Reactive primitives

Cell

Reactive variable in Warp9 may have a value or be empty, by default it empty

var a = new warp9.Cell();

To put a value to reactive variable you should use a "set" method

var a = new warp9.Cell();
a.set(42);

The alternative is to pass a value via constructor

var a = new warp9.Cell(42)

At any moment a variable may be set empty

var a = new wapr9.Cell(42);
a.unset()

A variable can be asked whether it has value

var a = new wapr9.Cell(42);
console.info(a.hasValue());
//> true

If a variable has value it can be gotten

var a = new wapr9.Cell(42);
console.info(a.get());
//> 42

If you try to get value of empty variable an exception will be thrown, unless you pass an default value to the get

console.info(new warp9.Cell().get(42));
//> 42

Cell.lift

You can apply a function to a reactive variable to get a new reactive variable which is bound with the first with that function. If the first is updated the second will be equal to a result of the function applied to the first's value.

var a = new warp9.Cell(); // a is empty
var b = a.lift(function(a){
    return a+2;
}); // b is empty
a.set(1); // a has 1, b has 3
a.set(5); // a has 5, b has 7
a.unset(); // a is empty, b is empty

There is a law for lift - for all f,x:

new warp9.Cell(x).lift(f).get()===f(x);

We can't use set & unset against variable we got as a result of lift call. Actually we can use set & unset only against reactive variables we got as a result of calling a warp9.Cell constructor.

Cell.coalesce

Each reactive variable has a coalesce method. It accepts one argument and returns a new reactive variable which has the same value as the first one, but if the first is empty, the second has a value equal to the argument.

var a = new warp9.Cell();
var b = a.coalesce(42); // b has 42
a.set(13); // a has 13, b has 13
a.unset(); // a is empty, b has 42

Cell.isSet

isSet returns a reactive variable which has true when a source has a value and false otherwise

var a = new warp9.Cell();
var b = a.isSet(); // b has false
a.set(13); // a has 13, b has true
a.unset(); // a is empty, b has false

Cell.when

Another way to create a variable is to call when method. If it is called with one argument, its argument is regarded as a filter. The filter is executed on each value change of a source variable, if the result is true then the created would have the same value or it would be empty otherwise.

var a = new warp9.Cell();
var b = a.when(function(a){
    return a>3;
}); // b is empty
a.set(42); // a has 42, b has 42
a.set(1); // a has 1, b is empty
a.set(4); // a has 4, b has 4
a.unset(); // a is empty, b is empty

You can use a value as the function. In such case it will be compared via === with the argument.

It is possible to pass two arguments to when method. The first is regarded as filter, the second as transformer. The transformer is applied to a value if the filter returns true, a result is written as a value to a created reactive variable. If the value of source variable is changed, the process if repeated.

var a = new warp9.Cell();
var b = a.when(
    function(a) { return a>3; },
    function(x) { x+1; },
); // b is empty
a.set(42); // a has 42, b has 43
a.set(1); // a has 1, b is empty
a.set(4); // a has 4, b has 5
a.unset(); // a is empty, b is empty

Such version of when(…, …) is almost equivalent to a combination of when(…) and lift(…). We may say that there is a law, for all f,t,cell:

cell.when(f,t)==cell.when(f).lift(t)

I say almost because instead of transformer-function we may pass a transformer-value, which yields itself on each "invocation".

var a = new warp9.Cell();
var b = a.when(42,13); // b is empty
a.set(42); // a has 42, b has 13
a.set(4); // a has 4, b is empty

The last form of when(…, …, …) accepts three arguments: filter, transformer and alternative transformer, which is applied if the result of the filter is false.

var a = new warp9.Cell();
var b = a.when(
    function(a) { return a>=0; },
    function(x) { return x+1; }
    function(x) { return x-1; }
); // b is empty
a.set(0); // a has 0, b has 1
a.set(-1); // a has -1, b has -2
a.unset(); // a is empty, b is empty

You also can pass a value instead of filter, transformer or alternative transformer, but if we omit it we may say that there a law, for all cell, f, t, a:

cell.when(f,t,a)==cell.lift(function(c) { return f(c) ? t(c) : a(c) });

warp9.do

The most powerful way to create reactive variables is to use warp9.do(…) construct. All previous ways (isSet, coalesce, lift and when) are implement via warp9.do(…).

Suppose you want to define a new reactive variable which value is equal to the sum of values of two another reactive variables. Also you want your new reactive variable to be automatically updated when any of two others changes. It can be done easily with warp9.do(…):

var a = new warp9.Cell(1);
var b = new warp9.Cell(2);
var sum = warp9.do(function(){
    return a.get() + b.get();
}); // sum has 3
a.set(2); // sum has 4
b.set(-2); // sum has 0

Warp9 tracks all variable you access inside the lambda and reevaluate it each time a dependency changes.

On any invocation the lambda may change its dependency. Lets see on example:

var a = new warp9.Cell();
var b = new warp9.Cell(1);
var c = new warp9.Cell(2);
var ternary = warp9.do(function(){
    return a.get() ? b.get() : c.get();
}); // ternary's only dependency is a
a.set(true); // ternary's dependencies are a,b
b.set(4);
a.set(false); // ternary's dependencies are a,c
a.unset(); // ternary's only dependency is a
            

I told that all form of when are implemented via warp9.do. Lets see how the when(…) may be implemented:

var cell = new warp9.Cell(42);
cell.when = function(filter) {
    return warp9.do(function(){
        return filter(this.get()) ? this.get() : warp9.empty();
    }, this);
};

You can see that warp9.do has two argument form, where the second argument is an context. Another thing you haven't seen before is warp9.empty(). Call it inside the warp9.do's lambda to unset the created reactive value.

List

By default list is empty

var list = new warp9.List();

You can add values to list via constructor

var list = new warp9.List([1,2,3]);

Or using an add method

var list = new warp9.List();
list.add("Warp9");
list.add("React");

You can access to the values of list via get

var list = new warp9.List();
list.add("Warp9");
list.add("React");
console.info(list.get());
// ["Warp9", "React"]

Method add returns an id, which can be used to remove as element.

var list = new warp9.List();
var warpId = list.add("Warp9");
var reactId = list.add("React");
console.info(list.get()) ;
// ["Warp9", "React"]
list.remove(reactId);
console.info(list.get());
// ["Warp9"]

Often it is needed to have an id as a part of an element, to achieve that you can pass a function to add method which will be executed by add method, the function will receive an id and the returned value will be inserted to the list.

var list = new warp9.List();
list.add(function(id) {
    return {id: id, name: "Warp9"};
});
console.info(list.get());
// [{id:15, name: "Warp9"}];

There is an another method to remove elements beside remove - removeWhich. It removes elements by predicate.

var list = new List([1,2,3]);
list.removeWhich(function(x){
    return x<2;
});
// list contains 2,3

Just like js's array warp9.List has forEach method

var list = new warp9.List(["Warp9", "React"]);
list.forEach(function(x){
    console.info(x);
});
// Warp9
// React

remove, removeWhich and forEach execute immediately and once, they do not have long reactive effect like Cell's lift.

List.lift

By the way List has its own version of lift, you may think of it as a reactive version of map.

var a = new warp9.List();
var b = a.lift(function(x) { return 2+x; });
var id1 = a.add(1);
// a has values [1], a has values [3]
var id2 = a.add(2);
// a has values [1, 2], a has values [3, 4]
a.remove(id1);
// a has values [2], a has values [4]

You can't call add, remove, removeWhich on list you got as a result of lift method.

List.reduce

Here I mentioned that support of reactive lists in Knockout and ReactiveCoffee is very limited if we compare it to Warp9. Let explore it. For a start we calculate the sum of elements in a list.

var list = new warp9.List();
var sum = list.reduce(0, function(a,b){
    return a+b;
}); // reduce returns a reactive variable
list.add(41); // sum has 41
list.add(1); // sum has 42

As we saw the sum reflects the changes to the list. Lets now calculate the count of the list elements.

var list = new warp9.List();
var count = list.lift(function(x){
    return 1;
}).reduce(0, function(a,b){
    return a+b;
});
var id41 = list.add(41); // count has 1
list.add(1); // count has 2
list.remove(id41); // count has 1

This code does what it should do, but it is not optimal. For example it creates an intermediate list. Warp9 provides an api to avoid it.

var list = new warp9.List();
var count = list.reduce(0, function(a,b){
    return a+b;
}, {
    wrap: function(x) { return 1; }
});
var id41 = list.add(41); // count has 1
list.add(1); // count has 2
list.remove(id41); // count has 1

wrap is executed for each element and the result is passed to further reduce logic.

Since we start talking about performance, what is the complexity of updating the "reduced" variable during list manipulation?

It it were Knockout the complexity would be O(n) in term of list length, because a reactive list in Knockout is an reactive variable whose value is an array and if you want to do a reactive aggregation you should subscribe and reevaluate aggregation on each change.

It Warp9 reactive list is an first class entity. Such approach allows to do some optimisation. For example, a complexity of updating a reduced value during list insert/remove is O(ln n).

But O(ln n) complexity is still too high for calculating count or sum during each list update, so Warp9 provides api to do it in O(1). If you see to Warp9's reduce method implementation, you will find something similar to

BaseList.prototype.reduce = function(identity, add, opt) {
    return this.reduceMonoid({
        identity: function() {return identity; },
        add: add
    }, opt);
};

If you are familiar with functional programming or abstract algebra you may guess that if we have reduceMonoid we may have reduceGroup. If you do so, you are right. So we can rewrite our example with sum from reduceMonoid (reduce) to reduceGroup

var sum = new list.reduceGroup({
    identity: function() { return 0; },
    add: function(a,b) { return a+b; },
    invert: function(x) { return -x; }
});

And the complexity falls from O(ln n) to O(1). The same we can do with count example

var count = new list.reduceGroup({
    identity: function() { return 0; },
    add: function(a,b) { return a+b; },
    invert: function(x) { return -x; }
}, {
    wrap: function(x) { return 1; }
});

Lets implement a bit sophisticated aggregator. Suppose our list contains boolean values and we want to do logical AND operation over a list's values. Since if know an aggregated value and a element to remove from aggregation we can't calculate a new aggregated value it seems that we should use reduceMonoid

var all = new list.reduceMonoid({
    identity: function() { return true; },
    add: function(a,b) { return a & b; }
});

But if we think we may come to idea to count "true" and "false" value independently. It allows us to calculate a new aggregated value if we know the last and removing element, so we can use reduceGroup

var all = new list.reduceGroup({
    identity: function() { return [0,0]; },
    add: function(x,y) { return [x[0]+y[0], x[1]+y[1]]; }
    invert: function(x) { return [-x[0], -x[1]]; }
}, {
    wrap: function(x) {
        return x ? [1,1] : [0,1];
    }
}).lift(function(x){
    return x[0]==x[1];
});

This code is much better since it is O(1) compared to O(ln n), but it still sucks because it needs an intermediate variable (we call lift on it), lets fix it, Warp9 provides a api for that

var all = new list.reduceGroup({
    identity: function() { return [0,0]; },
    add: function(x,y) { return [x[0]+y[0], x[1]+y[1]]; }
    invert: function(x) { return [-x[0], -x[1]]; }
}, {
    wrap: function(x) {
        return x ? [1,1] : [0,1];
    },
    unwrap: function(x) {
        return x[0]==x[1];
    }
});

Reactive list of reactive variables

The best part about reduceMonoid and reduceGroup is that they take into account if a list's element is a reactive variable. It means you can put a reactive variable to a reactive list, do aggregation and get a reactive variable which will be updated when the list changes or the reactive variable put to list changes. See an example

var list = new warp9.List();
var it1 = new warp9.Cell(0);
var it2 = new warp9.Cell(1);
var sum = list.reduce(0, function(a,b){
    return a+b;
}); // sum has 0
var itId1 = list.add(it1); // sum has 0
var itId2 = list.add(it2); // sum has 1
it1.set(5); // sum has 6
it2.set(2); // sum has 7
it1.unset(); // sum is empty
list.remove(it1); // sum has 2

You can see that if a reactive variable is empty in a list, then a reduced value is empty too. Sometimes it is useful to consider an empty variable as an identity element, you can do it with ignoreUnset option

var item1 = new warp9.Cell();
var item2 = new warp9.Cell(1);
var list = new warp9.List([item1, item2]);
var sum = list.reduce(0, function(a,b) {
    return a + b;
},{
    ignoreUnset : true
}); // sum has 1
item1.set(3); // sum has 4

Reactivity and leaks

Reactive model (aka observer pattern, publish-subscribe pattern, etc.) is great, it solves a lot of problems, for example it allows to build composable applications, but you should use it carefully, because it has its own pitfalls. One of them is memory leaks. If we look through a wiki article about memory leaks, we'll see:

To prevent this (memory leaks), the developer is responsible for cleaning up references after use ... and, if necessary, by deregistering any event listeners that maintain strong references to the object

This problem is actual for most observer pattern implementations. Martin Fowler mentions and a lot of articles are dedicated to it.

I believe when there are a lot problems of particular kind with code which uses a library this is the library's problem (even there is a workaround in the docs). So if you are designing a library you should think how to prevent errors or at least make them visible. I followed this principle when designed Warp9. With Warp9 your code has a leak related to reactivity if and only if you forget to remove subscription to variable you have subscribed to. It means you have an explicit way to test your code for memory leaks and a rule to avoid them.

Subscriptions

To subscribe to a reactive variable (Cell or List) you should call onChange method and pass a handler (a function) as an argument. This function will be called once after subscription and then each time the variable changes. On each invocation handler receives an object it subscribed to and an event.

To remove a subscription you should call a method returned by onChange.

When you deal with Cell, you can ignore the event and use cell's methods to recognise its state.

var cell = new warp9.Cell();
var dispose = cell.onChange(function(cell, event){
    if (!cell.hasValue()) {
        console.info("unset: event = " + JSON.stringify(event))
    } else {
        console.info("set: value = " + cell.get() + ", event = " + JSON.stringify(event));
    }
});
cell.set(1);
cell.unset();
dispose(); // <-- to prevent memory leak
cell.set(2);

This yields to console:

unset: event = ["unset"]
set: value = 1, event = ["set",1]
unset: event = ["unset"]

If your reactive object is variable (Cell) you have another methods to subscribe: .onSet(handler) and .on(obj, handler). Those methods also returns a method that removes the subscription. If you subscribe via onSet to a variable then your handler may be called once after subscription (if the variable has value) and then each time the variable is set, so you can be sure that it has value. on(obj, handler) has event stronger condition, your handler may be called once after subscription (if the variable has value equal to obj) and then each time when the variable is set to a value equal to obj.

Lets exam List's events

var list = new warp9.List();
var dispose = list.onChange(function(list, event){
    console.info(JSON.stringify(event));
});
var key42 = list.add(42);
var key13 = list.add(13);
list.remove(key13);
list.setData([1910, 1948]);
dispose(); // <-- to prevent memory leak
list.add(1900);

This yields to console something similar to:

["reset",[]]
["add",{"key":34,"value":42}]
["add",{"key":35,"value":13}]
["remove",35]
["reset",[{"key":36,"value":1910},{"key":37,"value":1948}]]

After subscription a handler will be called with "reset" event which argument reflects the content of the list at that moment (for each value a key is also provided). When an element is added the handler will be called with "add" event, also when an element is deleted it will be call with "remove" event, which argument is a key of removed element. The events are ordered, it means you can take the value of the last "reset" event, apply all subsequent "add" & "remove" events and get the current value of the list.