Skip to content

Instantly share code, notes, and snippets.

@davidmz
Created January 28, 2014 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidmz/8673464 to your computer and use it in GitHub Desktop.
Save davidmz/8673464 to your computer and use it in GitHub Desktop.
Простая FRP-библиотечка для JS, позволяющая писать код в Excell-стиле.
(function (undefined) {
"use strict";
var
isArray = Array.isArray || function (v) {
return Object.prototype.toString.call(v) === "[object Array]";
},
isFunction = function (v) {
return Object.prototype.toString.call(v) === "[object Function]"
},
emptyMaker = function () {
return Array.prototype.slice.call(arguments);
},
versionCounter = 0;
/**
* Cell function constructor
* @param {Array.<XCell>} [parents]
* @param {function(...*):*} [maker]
* @returns {XCell}
*/
var Cell = function (parents, maker) {
return new XCell(
isArray(parents) ? parents : [],
isFunction(maker) ? maker : emptyMaker
);
};
/**
* Cell object
* @param {Array.<XCell>} parents
* @param {function} maker
* @constructor
*/
var XCell = function (parents, maker) {
var value,
listeners = [],
version = versionCounter,
self = this;
/**
* Get current value
* @returns {*}
*/
this.value = function () {
if (parents.length == 0) return value;
var args = [], i;
for (i = 0; i < parents.length; i++) args.push(parents[i].value());
return maker.apply(this, args);
};
var updateListeners = function (val) { for (var i = 0; i < listeners.length; i++) listeners[i].call(null, val, version); };
/**
* Set value for scalar (without parents) cell
* @param {*} x
* @returns {XCell}
*/
this.set = function (x) {
if (parents.length == 0) {
value = x;
version = ++versionCounter;
}
updateListeners(this.value());
return this;
};
/**
* Function foo handles cell update
* @param {function(*)} foo
* @returns {XCell}
*/
this.onValue = function (foo) {
listeners.push(foo);
foo.call(null, this.value(), version);
return this;
};
/**
* Same as onValue, but value of cell treated as array and passed to foo as arguments
* @param {function(...*)} foo
* @returns {XCell}
*/
this.onValues = function (foo) {
return this.onValue(function (a) { foo.apply(null, a); });
};
//// Listening to parents ////
for (var i = 0; i < parents.length; i++) {
parents[i].onValue(function (_, ver) {
if (ver > version) {
version = ver;
updateListeners(self.value());
}
});
}
};
/////////////////////////////////////////
// Service prototype methods
/////////////////////////////////////////
/**
* Returns a new cell with the values filtered by foo
* Foo is function returns boolean
* @param {function(*):boolean} foo
* @returns {XCell}
*/
XCell.prototype.filter = function (foo) {
var cell = Cell();
this.onValue(function (v) {
if (foo(v)) cell.set(v);
});
return cell;
};
/**
* Returns a new cell with the values modified by foo
* @param {function(*):*|*} foo
* @returns {XCell}
*/
XCell.prototype.map = function (foo) {
if (isFunction(foo)) {
return Cell([this], foo);
} else {
return Cell([this], function () { return foo; });
}
};
/**
* Returns a new cell with values, selected from initial values by query
* Query is a string started with dot like ".foo", ".foo.bar", ".foo.0.bar"
* @param {string} query
* @returns {XCell}
*/
XCell.prototype.select = function (query) {
if (typeof query == "string" && query.charAt(0) == ".") {
var parts = query.substr(1).split(".");
return Cell([this], function (v) {
for (var i = 0; i < parts.length; i++) {
if (v instanceof Object && parts[i] in v) {
v = v[parts[i]];
} else {
return undefined;
}
}
return v;
});
} else {
return Cell([this], function () { return undefined; })
}
};
//noinspection JSUnusedGlobalSymbols
/**
* Fold cell values with given seed value and accumulator function foo
* @param {*} seed
* @param {function(*, *):*} foo
* @returns {XCell}
*/
XCell.prototype.fold = function (seed, foo) {
var self = this;
return Cell.construct(function (cell) {
cell.set(seed);
self.onValue(function (v) { cell.set(foo(cell.value(), v)); });
});
};
/////////////////////////////////////////
// 'Static' methods
/////////////////////////////////////////
/**
* Creates cell and initialises it with function foo
* @param {function(XCell)} foo
* @returns {XCell}
*/
Cell.construct = function (foo) {
var cell = Cell();
foo(cell);
return cell;
};
/////////////////////////////////////////
// THE END //
/////////////////////////////////////////
Cell.fn = XCell.prototype;
var _Cell = window.Cell;
window.Cell = Cell;
Cell.noConflict = function () {
if (window.Cell === Cell) {
window.Cell = _Cell;
}
return Cell;
};
})();
@davidmz
Copy link
Author

davidmz commented Jan 28, 2014

Пример:

var a = Cell().set(1),
    b = Cell().set(2),
    c = Cell([a, b], function(a, b) { return a + b; });

c.onValue(function(v) { console.log("c = ", v); }) // c = 3

a.set(2); // c = 4
b.set(5); // c = 7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment