sodiumjs
Version:
A Functional Reactive Programming (FRP) library for JavaScript
1,298 lines (1,280 loc) • 90 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var Collections = require('typescript-collections');
var Z = require('sanctuary-type-classes');
var Entry = /** @class */ (function () {
function Entry(rank, action) {
this.rank = rank;
this.action = action;
this.seq = Entry.nextSeq++;
}
Entry.prototype.toString = function () {
return this.seq.toString();
};
Entry.nextSeq = 0;
return Entry;
}());
var Transaction = /** @class */ (function () {
function Transaction() {
this.inCallback = 0;
this.toRegen = false;
this.prioritizedQ = new Collections.PriorityQueue(function (a, b) {
// Note: Low priority numbers are treated as "greater" according to this
// comparison, so that the lowest numbers are highest priority and go first.
if (a.rank.rank < b.rank.rank)
return 1;
if (a.rank.rank > b.rank.rank)
return -1;
if (a.seq < b.seq)
return 1;
if (a.seq > b.seq)
return -1;
return 0;
});
this.entries = new Collections.Set(function (a) { return a.toString(); });
this.sampleQ = [];
this.lastQ = [];
this.postQ = null;
}
Transaction.prototype.requestRegen = function () {
this.toRegen = true;
};
Transaction.prototype.prioritized = function (target, action) {
var e = new Entry(target, action);
this.prioritizedQ.enqueue(e);
this.entries.add(e);
};
Transaction.prototype.sample = function (h) {
this.sampleQ.push(h);
};
Transaction.prototype.last = function (h) {
this.lastQ.push(h);
};
Transaction._collectCyclesAtEnd = function () {
Transaction.run(function () { return Transaction.collectCyclesAtEnd = true; });
};
/**
* Add an action to run after all last() actions.
*/
Transaction.prototype.post = function (childIx, action) {
if (this.postQ == null)
this.postQ = [];
// If an entry exists already, combine the old one with the new one.
while (this.postQ.length <= childIx)
this.postQ.push(null);
var existing = this.postQ[childIx], neu = existing === null ? action
: function () {
existing();
action();
};
this.postQ[childIx] = neu;
};
// If the priority queue has entries in it when we modify any of the nodes'
// ranks, then we need to re-generate it to make sure it's up-to-date.
Transaction.prototype.checkRegen = function () {
if (this.toRegen) {
this.toRegen = false;
this.prioritizedQ.clear();
var es = this.entries.toArray();
for (var i = 0; i < es.length; i++)
this.prioritizedQ.enqueue(es[i]);
}
};
Transaction.prototype.isActive = function () {
return Transaction.currentTransaction ? true : false;
};
Transaction.prototype.close = function () {
while (true) {
while (true) {
this.checkRegen();
if (this.prioritizedQ.isEmpty())
break;
var e = this.prioritizedQ.dequeue();
this.entries.remove(e);
e.action();
}
var sq = this.sampleQ;
this.sampleQ = [];
for (var i = 0; i < sq.length; i++)
sq[i]();
if (this.prioritizedQ.isEmpty() && this.sampleQ.length < 1)
break;
}
for (var i = 0; i < this.lastQ.length; i++)
this.lastQ[i]();
this.lastQ = [];
if (this.postQ != null) {
for (var i = 0; i < this.postQ.length; i++) {
if (this.postQ[i] != null) {
var parent_1 = Transaction.currentTransaction;
try {
if (i > 0) {
Transaction.currentTransaction = new Transaction();
try {
this.postQ[i]();
Transaction.currentTransaction.close();
}
catch (err) {
Transaction.currentTransaction.close();
throw err;
}
}
else {
Transaction.currentTransaction = null;
this.postQ[i]();
}
Transaction.currentTransaction = parent_1;
}
catch (err) {
Transaction.currentTransaction = parent_1;
throw err;
}
}
}
this.postQ = null;
}
};
/**
* Add a runnable that will be executed whenever a transaction is started.
* That runnable may start transactions itself, which will not cause the
* hooks to be run recursively.
*
* The main use case of this is the implementation of a time/alarm system.
*/
Transaction.onStart = function (r) {
Transaction.onStartHooks.push(r);
};
Transaction.run = function (f) {
var transWas = Transaction.currentTransaction;
if (transWas === null) {
if (!Transaction.runningOnStartHooks) {
Transaction.runningOnStartHooks = true;
try {
for (var i = 0; i < Transaction.onStartHooks.length; i++)
Transaction.onStartHooks[i]();
}
finally {
Transaction.runningOnStartHooks = false;
}
}
Transaction.currentTransaction = new Transaction();
}
try {
var a = f();
if (transWas === null) {
Transaction.currentTransaction.close();
Transaction.currentTransaction = null;
if (Transaction.collectCyclesAtEnd) {
Vertex.collectCycles();
Transaction.collectCyclesAtEnd = false;
}
}
return a;
}
catch (err) {
if (transWas === null) {
Transaction.currentTransaction.close();
Transaction.currentTransaction = null;
}
throw err;
}
};
Transaction.currentTransaction = null;
Transaction.onStartHooks = [];
Transaction.runningOnStartHooks = false;
Transaction.collectCyclesAtEnd = false;
return Transaction;
}());
var totalRegistrations = 0;
function getTotalRegistrations() {
return totalRegistrations;
}
var Source = /** @class */ (function () {
// Note:
// When register_ == null, a rank-independent source is constructed (a vertex which is just kept alive for the
// lifetime of vertex that contains this source).
// When register_ != null it is likely to be a rank-dependent source, but this will depend on the code inside register_.
//
// rank-independent souces DO NOT bump up the rank of the vertex containing those sources.
// rank-depdendent sources DO bump up the rank of the vertex containing thoses sources when required.
function Source(origin, register_) {
this.registered = false;
this.deregister_ = null;
if (origin === null)
throw new Error("null origin!");
this.origin = origin;
this.register_ = register_;
}
Source.prototype.register = function (target) {
var _this = this;
if (!this.registered) {
this.registered = true;
if (this.register_ !== null)
this.deregister_ = this.register_();
else {
// Note: The use of Vertex.NULL here instead of "target" is not a bug, this is done to create a
// rank-independent source. (see note at constructor for more details.). The origin vertex still gets
// added target vertex's children for the memory management algorithm.
this.origin.increment(Vertex.NULL);
target.childrn.push(this.origin);
this.deregister_ = function () {
_this.origin.decrement(Vertex.NULL);
for (var i = target.childrn.length - 1; i >= 0; --i) {
if (target.childrn[i] === _this.origin) {
target.childrn.splice(i, 1);
break;
}
}
};
}
}
};
Source.prototype.deregister = function (target) {
if (this.registered) {
this.registered = false;
if (this.deregister_ !== null)
this.deregister_();
}
};
return Source;
}());
var Color;
(function (Color) {
Color[Color["black"] = 0] = "black";
Color[Color["gray"] = 1] = "gray";
Color[Color["white"] = 2] = "white";
Color[Color["purple"] = 3] = "purple";
})(Color || (Color = {}));
var roots = [];
var nextID = 0;
var verbose = false;
var Vertex = /** @class */ (function () {
function Vertex(name, rank, sources) {
this.targets = [];
this.childrn = [];
this.visited = false;
// --------------------------------------------------------
// Synchronous Cycle Collection algorithm presented in "Concurrent
// Cycle Collection in Reference Counted Systems" by David F. Bacon
// and V.T. Rajan.
this.color = Color.black;
this.buffered = false;
this.refCountAdj = 0;
this.name = name;
this.rank = rank;
this.sources = sources;
this.id = nextID++;
}
Vertex.prototype.refCount = function () { return this.targets.length; };
Vertex.prototype.register = function (target) {
return this.increment(target);
};
Vertex.prototype.deregister = function (target) {
if (verbose)
console.log("deregister " + this.descr() + " => " + target.descr());
this.decrement(target);
Transaction._collectCyclesAtEnd();
};
Vertex.prototype.incRefCount = function (target) {
var anyChanged = false;
if (this.refCount() == 0) {
for (var i = 0; i < this.sources.length; i++)
this.sources[i].register(this);
}
this.targets.push(target);
target.childrn.push(this);
if (target.ensureBiggerThan(this.rank))
anyChanged = true;
totalRegistrations++;
return anyChanged;
};
Vertex.prototype.decRefCount = function (target) {
if (verbose)
console.log("DEC " + this.descr());
var matched = false;
for (var i = target.childrn.length - 1; i >= 0; i--)
if (target.childrn[i] === this) {
target.childrn.splice(i, 1);
break;
}
for (var i = 0; i < this.targets.length; i++)
if (this.targets[i] === target) {
this.targets.splice(i, 1);
matched = true;
break;
}
if (matched) {
if (this.refCount() == 0) {
for (var i = 0; i < this.sources.length; i++)
this.sources[i].deregister(this);
}
totalRegistrations--;
}
};
Vertex.prototype.addSource = function (src) {
this.sources.push(src);
if (this.refCount() > 0)
src.register(this);
};
Vertex.prototype.ensureBiggerThan = function (limit) {
if (this.visited) {
// Undoing cycle detection for now until TimerSystem.ts ranks are checked.
//throw new Error("Vertex cycle detected.");
return false;
}
if (this.rank > limit)
return false;
this.visited = true;
this.rank = limit + 1;
for (var i = 0; i < this.targets.length; i++)
this.targets[i].ensureBiggerThan(this.rank);
this.visited = false;
return true;
};
Vertex.prototype.descr = function () {
var colStr = null;
switch (this.color) {
case Color.black:
colStr = "black";
break;
case Color.gray:
colStr = "gray";
break;
case Color.white:
colStr = "white";
break;
case Color.purple:
colStr = "purple";
break;
}
var str = this.id + " " + this.name + " [" + this.refCount() + "/" + this.refCountAdj + "] " + colStr + " ->";
var chs = this.children();
for (var i = 0; i < chs.length; i++) {
str = str + " " + chs[i].id;
}
return str;
};
Vertex.prototype.children = function () { return this.childrn; };
Vertex.prototype.increment = function (referrer) {
return this.incRefCount(referrer);
};
Vertex.prototype.decrement = function (referrer) {
this.decRefCount(referrer);
if (this.refCount() == 0)
this.release();
else
this.possibleRoots();
};
Vertex.prototype.release = function () {
this.color = Color.black;
if (!this.buffered)
this.free();
};
Vertex.prototype.free = function () {
while (this.targets.length > 0)
this.decRefCount(this.targets[0]);
};
Vertex.prototype.possibleRoots = function () {
if (this.color != Color.purple) {
this.color = Color.purple;
if (!this.buffered) {
this.buffered = true;
roots.push(this);
}
}
};
Vertex.collectCycles = function () {
if (Vertex.collectingCycles) {
return;
}
try {
Vertex.collectingCycles = true;
Vertex.markRoots();
Vertex.scanRoots();
Vertex.collectRoots();
for (var i = Vertex.toBeFreedList.length - 1; i >= 0; --i) {
var vertex = Vertex.toBeFreedList.splice(i, 1)[0];
vertex.free();
}
}
finally {
Vertex.collectingCycles = false;
}
};
Vertex.markRoots = function () {
var newRoots = [];
// check refCountAdj was restored to zero before mark roots
if (verbose) {
var stack = roots.slice(0);
var visited = new Collections.Set();
while (stack.length != 0) {
var vertex = stack.pop();
if (visited.contains(vertex.id)) {
continue;
}
visited.add(vertex.id);
if (vertex.refCountAdj != 0) {
console.log("markRoots(): reachable refCountAdj was not reset to zero: " + vertex.descr());
}
for (var i = 0; i < vertex.childrn.length; ++i) {
var child = vertex.childrn[i];
stack.push(child);
}
}
}
//
for (var i = 0; i < roots.length; i++) {
if (verbose)
console.log("markRoots " + roots[i].descr()); // ###
if (roots[i].color == Color.purple) {
roots[i].markGray();
newRoots.push(roots[i]);
}
else {
roots[i].buffered = false;
if (roots[i].color == Color.black && roots[i].refCount() == 0)
Vertex.toBeFreedList.push(roots[i]);
}
}
roots = newRoots;
};
Vertex.scanRoots = function () {
for (var i = 0; i < roots.length; i++)
roots[i].scan();
};
Vertex.collectRoots = function () {
for (var i = 0; i < roots.length; i++) {
roots[i].buffered = false;
roots[i].collectWhite();
}
if (verbose) { // double check adjRefCount is zero for all vertices reachable by roots
var stack = roots.slice(0);
var visited = new Collections.Set();
while (stack.length != 0) {
var vertex = stack.pop();
if (visited.contains(vertex.id)) {
continue;
}
visited.add(vertex.id);
if (vertex.refCountAdj != 0) {
console.log("collectRoots(): reachable refCountAdj was not reset to zero: " + vertex.descr());
}
for (var i = 0; i < vertex.childrn.length; ++i) {
var child = vertex.childrn[i];
stack.push(child);
}
}
}
roots = [];
};
Vertex.prototype.markGray = function () {
if (this.color != Color.gray) {
this.color = Color.gray;
var chs = this.children();
for (var i = 0; i < chs.length; i++) {
chs[i].refCountAdj--;
if (verbose)
console.log("markGray " + this.descr());
chs[i].markGray();
}
}
};
Vertex.prototype.scan = function () {
if (verbose)
console.log("scan " + this.descr());
if (this.color == Color.gray) {
if (this.refCount() + this.refCountAdj > 0)
this.scanBlack();
else {
this.color = Color.white;
if (verbose)
console.log("scan WHITE " + this.descr());
var chs = this.children();
for (var i = 0; i < chs.length; i++)
chs[i].scan();
}
}
};
Vertex.prototype.scanBlack = function () {
this.refCountAdj = 0;
this.color = Color.black;
var chs = this.children();
for (var i = 0; i < chs.length; i++) {
if (verbose)
console.log("scanBlack " + this.descr());
if (chs[i].color != Color.black)
chs[i].scanBlack();
}
};
Vertex.prototype.collectWhite = function () {
if (this.color == Color.white && !this.buffered) {
if (verbose)
console.log("collectWhite " + this.descr());
this.color = Color.black;
this.refCountAdj = 0;
var chs = this.children();
for (var i = 0; i < chs.length; i++)
chs[i].collectWhite();
Vertex.toBeFreedList.push(this);
}
};
Vertex.NULL = new Vertex("user", 1e12, []);
Vertex.collectingCycles = false;
Vertex.toBeFreedList = [];
return Vertex;
}());
var Lambda1 = /** @class */ (function () {
function Lambda1(f, deps) {
this.f = f;
this.deps = deps;
}
return Lambda1;
}());
function lambda1(f, deps) {
return new Lambda1(f, deps);
}
function Lambda1_deps(f) {
if (f instanceof Lambda1)
return f.deps;
else
return [];
}
function Lambda1_toFunction(f) {
if (f instanceof Lambda1)
return f.f;
else
return f;
}
var Lambda2 = /** @class */ (function () {
function Lambda2(f, deps) {
this.f = f;
this.deps = deps;
}
return Lambda2;
}());
function lambda2(f, deps) {
return new Lambda2(f, deps);
}
function Lambda2_deps(f) {
if (f instanceof Lambda2)
return f.deps;
else
return [];
}
function Lambda2_toFunction(f) {
if (f instanceof Lambda2)
return f.f;
else
return f;
}
var Lambda3 = /** @class */ (function () {
function Lambda3(f, deps) {
this.f = f;
this.deps = deps;
}
return Lambda3;
}());
function lambda3(f, deps) {
return new Lambda3(f, deps);
}
function Lambda3_deps(f) {
if (f instanceof Lambda3)
return f.deps;
else
return [];
}
function Lambda3_toFunction(f) {
if (f instanceof Lambda3)
return f.f;
else
return f;
}
var Lambda4 = /** @class */ (function () {
function Lambda4(f, deps) {
this.f = f;
this.deps = deps;
}
return Lambda4;
}());
function lambda4(f, deps) {
return new Lambda4(f, deps);
}
function Lambda4_deps(f) {
if (f instanceof Lambda4)
return f.deps;
else
return [];
}
function Lambda4_toFunction(f) {
if (f instanceof Lambda4)
return f.f;
else
return f;
}
var Lambda5 = /** @class */ (function () {
function Lambda5(f, deps) {
this.f = f;
this.deps = deps;
}
return Lambda5;
}());
function lambda5(f, deps) {
return new Lambda5(f, deps);
}
function Lambda5_deps(f) {
if (f instanceof Lambda5)
return f.deps;
else
return [];
}
function Lambda5_toFunction(f) {
if (f instanceof Lambda5)
return f.f;
else
return f;
}
var Lambda6 = /** @class */ (function () {
function Lambda6(f, deps) {
this.f = f;
this.deps = deps;
}
return Lambda6;
}());
function lambda6(f, deps) {
return new Lambda6(f, deps);
}
function Lambda6_deps(f) {
if (f instanceof Lambda6)
return f.deps;
else
return [];
}
function Lambda6_toFunction(f) {
if (f instanceof Lambda6)
return f.f;
else
return f;
}
function toSources(deps) {
var ss = [];
for (var i = 0; i < deps.length; i++) {
var dep = deps[i];
ss.push(new Source(dep.getVertex__(), null));
}
return ss;
}
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
/**
* A representation for a value that may not be available until the current
* transaction is closed.
*/
var Lazy = /** @class */ (function () {
function Lazy(f) {
this.f = f;
}
/**
* Get the value if available, throwing an exception if not.
* In the general case this should only be used in subsequent transactions to
* when the Lazy was obtained.
*/
Lazy.prototype.get = function () {
return this.f();
};
/**
* Map the lazy value according to the specified function, so the returned Lazy reflects
* the value of the function applied to the input Lazy's value.
* @param f Function to apply to the contained value. It must be <em>referentially transparent</em>.
*/
Lazy.prototype.map = function (f) {
var _this = this;
return new Lazy(function () { return f(_this.f()); });
};
/**
* Lift a binary function into lazy values, so the returned Lazy reflects
* the value of the function applied to the input Lazys' values.
*/
Lazy.prototype.lift = function (b, f) {
var _this = this;
return new Lazy(function () { return f(_this.f(), b.f()); });
};
/**
* Lift a ternary function into lazy values, so the returned Lazy reflects
* the value of the function applied to the input Lazys' values.
*/
Lazy.prototype.lift3 = function (b, c, f) {
var _this = this;
return new Lazy(function () { return f(_this.f(), b.f(), c.f()); });
};
/**
* Lift a quaternary function into lazy values, so the returned Lazy reflects
* the value of the function applied to the input Lazys' values.
*/
Lazy.prototype.lift4 = function (b, c, d, f) {
var _this = this;
return new Lazy(function () { return f(_this.f(), b.f(), c.f(), d.f()); });
};
return Lazy;
}());
var Unit = /** @class */ (function () {
function Unit() {
}
Unit.UNIT = new Unit();
return Unit;
}());
var Operational = /** @class */ (function () {
function Operational() {
}
/**
* A stream that gives the updates/steps for a {@link Cell}.
* <P>
* This is an OPERATIONAL primitive, which is not part of the main Sodium
* API. It breaks the property of non-detectability of cell steps/updates.
* The rule with this primitive is that you should only use it in functions
* that do not allow the caller to detect the cell updates.
*/
Operational.updates = function (c) {
/* Don't think this is needed
const out = new StreamWithSend<A>(null);
out.setVertex__(new Vertex("updates", 0, [
new Source(
c.getStream__().getVertex__(),
() => {
return c.getStream__().listen_(out.getVertex__(), (a : A) => {
out.send_(a);
}, false);
}
),
new Source(
c.getVertex__(),
() => {
return () => { };
}
)
]
));
return out;
*/
return c.getStream__();
};
/**
* A stream that is guaranteed to fire once in the transaction where value() is invoked, giving
* the current value of the cell, and thereafter behaves like {@link updates(Cell)},
* firing for each update/step of the cell's value.
* <P>
* This is an OPERATIONAL primitive, which is not part of the main Sodium
* API. It breaks the property of non-detectability of cell steps/updates.
* The rule with this primitive is that you should only use it in functions
* that do not allow the caller to detect the cell updates.
*/
Operational.value = function (c) {
return Transaction.run(function () {
var sSpark = new StreamWithSend();
Transaction.currentTransaction.prioritized(sSpark.getVertex__(), function () {
sSpark.send_(Unit.UNIT);
});
var sInitial = sSpark.snapshot1(c);
return Operational.updates(c).orElse(sInitial);
});
};
/**
* Push each event onto a new transaction guaranteed to come before the next externally
* initiated transaction. Same as {@link split(Stream)} but it works on a single value.
*/
Operational.defer = function (s) {
return Operational.split(s.map(function (a) {
return [a];
}));
};
/**
* Push each event in the list onto a newly created transaction guaranteed
* to come before the next externally initiated transaction. Note that the semantics
* are such that two different invocations of split() can put events into the same
* new transaction, so the resulting stream's events could be simultaneous with
* events output by split() or {@link defer(Stream)} invoked elsewhere in the code.
*/
Operational.split = function (s) {
var out = new StreamWithSend(null);
out.setVertex__(new Vertex("split", 0, [
new Source(s.getVertex__(), function () {
out.getVertex__().childrn.push(s.getVertex__());
var cleanups = [];
cleanups.push(s.listen_(Vertex.NULL, function (as) {
var _loop_1 = function (i) {
Transaction.currentTransaction.post(i, function () {
Transaction.run(function () {
out.send_(as[i]);
});
});
};
for (var i = 0; i < as.length; i++) {
_loop_1(i);
}
}, false));
cleanups.push(function () {
var chs = out.getVertex__().childrn;
for (var i = chs.length - 1; i >= 0; --i) {
if (chs[i] == s.getVertex__()) {
chs.splice(i, 1);
break;
}
}
});
return function () {
cleanups.forEach(function (cleanup) { return cleanup(); });
cleanups.splice(0, cleanups.length);
};
})
]));
return out;
};
return Operational;
}());
var Tuple2 = /** @class */ (function () {
function Tuple2(a, b) {
this.a = a;
this.b = b;
}
return Tuple2;
}());
var LazySample = /** @class */ (function () {
function LazySample(cell) {
this.hasValue = false;
this.value = null;
this.cell = cell;
}
return LazySample;
}());
var ApplyState = /** @class */ (function () {
function ApplyState() {
this.f = null;
this.f_present = false;
this.a = null;
this.a_present = false;
}
return ApplyState;
}());
var Cell = /** @class */ (function () {
function Cell(initValue, str) {
var _this = this;
this.value = initValue;
if (!str) {
this.str = new Stream();
this.vertex = new Vertex("ConstCell", 0, []);
}
else
Transaction.run(function () { return _this.setStream(str); });
}
Cell.prototype.setStream = function (str) {
var _this = this;
this.str = str;
var me = this, src = new Source(str.getVertex__(), function () {
return str.listen_(me.vertex, function (a) {
if (me.valueUpdate == null) {
Transaction.currentTransaction.last(function () {
me.value = me.valueUpdate;
me.lazyInitValue = null;
me.valueUpdate = null;
});
}
me.valueUpdate = a;
}, false);
});
this.vertex = new Vertex("Cell", 0, [src]);
// We do a trick here of registering the source for the duration of the current
// transaction so that we are guaranteed to catch any stream events that
// occur in the same transaction.
//
// A new temporary vertex null is constructed here as a performance work-around to avoid
// having too many children in Vertex.NULL as a deregister operation is O(n^2) where
// n is the number of children in the vertex.
var tmpVertexNULL = new Vertex("Cell::setStream", 1e12, []);
this.vertex.register(tmpVertexNULL);
Transaction.currentTransaction.last(function () {
_this.vertex.deregister(tmpVertexNULL);
});
};
Cell.prototype.getVertex__ = function () {
return this.vertex;
};
Cell.prototype.getStream__ = function () {
return this.str;
};
/**
* Sample the cell's current value.
* <p>
* It should generally be avoided in favour of {@link listen(Handler)} so you don't
* miss any updates, but in many circumstances it makes sense.
* <p>
* NOTE: In the Java and other versions of Sodium, using sample() inside map(), filter() and
* merge() is encouraged. In the Javascript/Typescript version, not so much, for the
* following reason: The memory management is different in the Javascript version, and this
* requires us to track all dependencies. In order for the use of sample() inside
* a closure to be correct, the cell that was sample()d inside the closure would have to be
* declared explicitly using the helpers lambda1(), lambda2(), etc. Because this is
* something that can be got wrong, we don't encourage this kind of use of sample() in
* Javascript. Better and simpler to use snapshot().
* <p>
* NOTE: If you need to sample() a cell, you have to make sure it's "alive" in terms of
* memory management or it will ignore updates. To make a cell work correctly
* with sample(), you have to ensure that it's being used. One way to guarantee this is
* to register a dummy listener on the cell. It will also work to have it referenced
* by something that is ultimately being listened to.
*/
Cell.prototype.sample = function () {
var _this = this;
return Transaction.run(function () { return _this.sampleNoTrans__(); });
};
Cell.prototype.sampleNoTrans__ = function () {
return this.value;
};
/**
* A variant of {@link sample()} that works with {@link CellLoop}s when they haven't been looped yet.
* It should be used in any code that's general enough that it could be passed a {@link CellLoop}.
* @see Stream#holdLazy(Lazy) Stream.holdLazy()
*/
Cell.prototype.sampleLazy = function () {
var me = this;
return Transaction.run(function () { return me.sampleLazyNoTrans__(); });
};
Cell.prototype.sampleLazyNoTrans__ = function () {
var me = this, s = new LazySample(me);
Transaction.currentTransaction.sample(function () {
s.value = me.valueUpdate != null ? me.valueUpdate : me.sampleNoTrans__();
s.hasValue = true;
s.cell = null;
});
return new Lazy(function () {
if (s.hasValue)
return s.value;
else
return s.cell.sample();
});
};
/**
* Transform the cell's value according to the supplied function, so the returned Cell
* always reflects the value of the function applied to the input Cell's value.
* @param f Function to apply to convert the values. It must be <em>referentially transparent</em>.
*/
Cell.prototype.map = function (f) {
var c = this;
return Transaction.run(function () {
return Operational.updates(c).map(f).holdLazy(c.sampleLazy().map(Lambda1_toFunction(f)));
});
};
/**
* Lift a binary function into cells, so the returned Cell always reflects the specified
* function applied to the input cells' values.
* @param fn Function to apply. It must be <em>referentially transparent</em>.
*/
Cell.prototype.lift = function (b, fn0) {
var fn = Lambda2_toFunction(fn0), cf = this.map(function (aa) { return function (bb) { return fn(aa, bb); }; });
return Cell.apply(cf, b, toSources(Lambda2_deps(fn0)));
};
/**
* Lift a ternary function into cells, so the returned Cell always reflects the specified
* function applied to the input cells' values.
* @param fn Function to apply. It must be <em>referentially transparent</em>.
*/
Cell.prototype.lift3 = function (b, c, fn0) {
var fn = Lambda3_toFunction(fn0), mf = function (aa) { return function (bb) { return function (cc) { return fn(aa, bb, cc); }; }; }, cf = this.map(mf);
return Cell.apply(Cell.apply(cf, b), c, toSources(Lambda3_deps(fn0)));
};
/**
* Lift a quaternary function into cells, so the returned Cell always reflects the specified
* function applied to the input cells' values.
* @param fn Function to apply. It must be <em>referentially transparent</em>.
*/
Cell.prototype.lift4 = function (b, c, d, fn0) {
var fn = Lambda4_toFunction(fn0), mf = function (aa) { return function (bb) { return function (cc) { return function (dd) { return fn(aa, bb, cc, dd); }; }; }; }, cf = this.map(mf);
return Cell.apply(Cell.apply(Cell.apply(cf, b), c), d, toSources(Lambda4_deps(fn0)));
};
/**
* Lift a 5-argument function into cells, so the returned Cell always reflects the specified
* function applied to the input cells' values.
* @param fn Function to apply. It must be <em>referentially transparent</em>.
*/
Cell.prototype.lift5 = function (b, c, d, e, fn0) {
var fn = Lambda5_toFunction(fn0), mf = function (aa) { return function (bb) { return function (cc) { return function (dd) { return function (ee) { return fn(aa, bb, cc, dd, ee); }; }; }; }; }, cf = this.map(mf);
return Cell.apply(Cell.apply(Cell.apply(Cell.apply(cf, b), c), d), e, toSources(Lambda5_deps(fn0)));
};
/**
* Lift a 6-argument function into cells, so the returned Cell always reflects the specified
* function applied to the input cells' values.
* @param fn Function to apply. It must be <em>referentially transparent</em>.
*/
Cell.prototype.lift6 = function (b, c, d, e, f, fn0) {
var fn = Lambda6_toFunction(fn0), mf = function (aa) { return function (bb) { return function (cc) { return function (dd) { return function (ee) { return function (ff) { return fn(aa, bb, cc, dd, ee, ff); }; }; }; }; }; }, cf = this.map(mf);
return Cell.apply(Cell.apply(Cell.apply(Cell.apply(Cell.apply(cf, b), c), d), e), f, toSources(Lambda6_deps(fn0)));
};
/**
* High order depenency traking. If any newly created sodium objects within a value of a cell of a sodium object
* happen to accumulate state, this method will keep the accumulation of state up to date.
*/
Cell.prototype.tracking = function (extractor) {
var _this = this;
var out = new StreamWithSend(null);
var vertex = new Vertex("tracking", 0, [
new Source(this.vertex, function () {
var cleanup2 = function () { };
var updateDeps = function (a) {
var lastCleanups2 = cleanup2;
var deps = extractor(a).map(function (dep) { return dep.getVertex__(); });
for (var i = 0; i < deps.length; ++i) {
var dep = deps[i];
vertex.childrn.push(dep);
dep.increment(Vertex.NULL);
}
cleanup2 = function () {
for (var i = 0; i < deps.length; ++i) {
var dep = deps[i];
for (var j = 0; j < vertex.childrn.length; ++j) {
if (vertex.childrn[j] === dep) {
vertex.childrn.splice(j, 1);
break;
}
}
dep.decrement(Vertex.NULL);
}
};
lastCleanups2();
};
updateDeps(_this.sample());
var cleanup1 = Operational.updates(_this).listen_(vertex, function (a) {
updateDeps(a);
out.send_(a);
}, false);
return function () {
cleanup1();
cleanup2();
};
})
]);
out.setVertex__(vertex);
return out.holdLazy(this.sampleLazy());
};
/**
* Lift an array of cells into a cell of an array.
*/
Cell.liftArray = function (ca) {
return Cell._liftArray(ca, 0, ca.length);
};
Cell._liftArray = function (ca, fromInc, toExc) {
if (toExc - fromInc == 0) {
return new Cell([]);
}
else if (toExc - fromInc == 1) {
return ca[fromInc].map(function (a) { return [a]; });
}
else {
var pivot = Math.floor((fromInc + toExc) / 2);
// the thunk boxing/unboxing here is a performance hack for lift when there are simutaneous changing cells.
return Cell._liftArray(ca, fromInc, pivot).lift(Cell._liftArray(ca, pivot, toExc), function (array1, array2) { return function () { return array1.concat(array2); }; })
.map(function (x) { return x(); });
}
};
/**
* Apply a value inside a cell to a function inside a cell. This is the
* primitive for all function lifting.
*/
Cell.apply = function (cf, ca, sources) {
return Transaction.run(function () {
var pumping = false;
var state = new ApplyState(), out = new StreamWithSend(), cf_updates = Operational.updates(cf), ca_updates = Operational.updates(ca), pump = function () {
if (pumping) {
return;
}
pumping = true;
Transaction.currentTransaction.prioritized(out.getVertex__(), function () {
var f = state.f_present ? state.f : cf.sampleNoTrans__();
var a = state.a_present ? state.a : ca.sampleNoTrans__();
out.send_(f(a));
pumping = false;
});
}, src1 = new Source(cf_updates.getVertex__(), function () {
return cf_updates.listen_(out.getVertex__(), function (f) {
state.f = f;
state.f_present = true;
pump();
}, false);
}), src2 = new Source(ca_updates.getVertex__(), function () {
return ca_updates.listen_(out.getVertex__(), function (a) {
state.a = a;
state.a_present = true;
pump();
}, false);
});
out.setVertex__(new Vertex("apply", 0, [src1, src2].concat(sources ? sources : [])));
return out.holdLazy(new Lazy(function () {
return cf.sampleNoTrans__()(ca.sampleNoTrans__());
}));
});
};
/**
* Unwrap a cell inside another cell to give a time-varying cell implementation.
*/
Cell.switchC = function (cca) {
return Transaction.run(function () {
var za = cca.sampleLazy().map(function (ba) { return ba.sample(); }), out = new StreamWithSend();
var outValue = null;
var pumping = false;
var pump = function () {
if (pumping) {
return;
}
pumping = true;
Transaction.currentTransaction.prioritized(out.getVertex__(), function () {
out.send_(outValue);
outValue = null;
pumping = false;
});
};
var last_ca = null;
var cca_value = Operational.value(cca), src = new Source(cca_value.getVertex__(), function () {
var kill2 = last_ca === null ? null :
Operational.value(last_ca).listen_(out.getVertex__(), function (a) { outValue = a; pump(); }, false);
var kill1 = cca_value.listen_(out.getVertex__(), function (ca) {
last_ca = ca;
// Connect before disconnect to avoid memory bounce, when switching to same cell twice.
var nextKill2 = Operational.value(ca).listen_(out.getVertex__(), function (a) {
outValue = a;
pump();
}, false);
if (kill2 !== null)
kill2();
kill2 = nextKill2;
}, false);
return function () { kill1(); kill2(); };
});
out.setVertex__(new Vertex("switchC", 0, [src]));
return out.holdLazy(za);
});
};
/**
* Unwrap a stream inside a cell to give a time-varying stream implementation.
*/
Cell.switchS = function (csa) {
return Transaction.run(function () {
var out = new StreamWithSend(), h2 = function (a) {
out.send_(a);
}, src = new Source(csa.getVertex__(), function () {
var kill2 = csa.sampleNoTrans__().listen_(out.getVertex__(), h2, false);
var kill1 = csa.getStream__().listen_(out.getVertex__(), function (sa) {
// Connect before disconnect to avoid memory bounce, when switching to same stream twice.
var nextKill2 = sa.listen_(out.getVertex__(), h2, true);
kill2();
kill2 = nextKill2;
}, false);
return function () { kill1(); kill2(); };
});
out.setVertex__(new Vertex("switchS", 0, [src]));
return out;
});
};
/**
* When transforming a value from a larger type to a smaller type, it is likely for duplicate changes to become
* propergated. This function insures only distinct changes get propergated.
*/
Cell.prototype.calm = function (eq) {
return Operational
.updates(this)
.collectLazy(this.sampleLazy(), function (newValue, oldValue) {
var result;
if (eq(newValue, oldValue)) {
result = null;
}
else {
result = newValue;
}
return new Tuple2(result, newValue);
})
.filterNotNull()
.holdLazy(this.sampleLazy());
};
/**
* This function is the same as calm, except you do not need to pass an eq function. This function will use (===)
* as its eq function. I.E. calling calmRefEq() is the same as calm((a,b) => a === b).
*/
Cell.prototype.calmRefEq = function () {
return this.calm(function (a, b) { return a === b; });
};
/**
* Listen for updates to the value of this cell. This is the observer pattern. The
* returned {@link Listener} has a {@link Listener#unlisten()} method to cause the
* listener to be removed. This is an OPERATIONAL mechanism is for interfacing between
* the world of I/O and for FRP.
* @param h The handler to execute when there's a new value.
* You should make no assumptions about what thread you are called on, and the
* handler should not block. You are not allowed to use {@link CellSink#send(Object)}
* or {@link StreamSink#send(Object)} in the handler.
* An exception will be thrown, because you are not meant to use this to create
* your own primitives.
*/
Cell.prototype.listen = function (h) {
var _this = this;
return Transaction.run(function () {
return Operational.value(_this).listen(h);
});
};
/**
* Fantasy-land Algebraic Data Type Compatability.
* Cell satisfies the Functor, Apply, Applicative categories
* @see {@link https://github.com/fantasyland/fantasy-land} for more info
*/
//of :: Applicative f => a -> f a
Cell['fantasy-land/of'] = function (a) {
return new Cell(a);
};
//map :: Functor f => f a ~> (a -> b) -> f b
Cell.prototype['fantasy-land/map'] = function (f) {
return this.map(f);
};
//ap :: Apply f => f a ~> f (a -> b) -> f b
Cell.prototype['fantasy-land/ap'] = function (cf) {
return Cell.apply(cf, this);
};
return Cell;
}());
var Listener = /** @class */ (function () {
function Listener(h, target) {
this.h = h;
this.target = target;
}
return Listener;
}());
var LazyCell = /** @class */ (function (_super) {
__extends(LazyCell, _super);
function LazyCell(lazyInitValue, str) {
var _this = _super.call(this, null, null) || this;
Transaction.run(function () {
if (str)
_this.setStream(str);
_this.lazyInitValue = lazyInitValue;
});
return _this;
}
LazyCell.prototype.sampleNoTrans__ = function () {
if (this.value == null && this.lazyInitValue != null) {
this.value = this.lazyInitValue.get();