UNPKG

sodiumjs

Version:

A Functional Reactive Programming (FRP) library for JavaScript

1,298 lines (1,282 loc) 89.4 kB
import { Dictionary, Set, PriorityQueue, BSTree } from 'typescript-collections'; import { Semigroup, concat } from '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 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 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 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 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(); this.lazyInitValue = null; } return this.value; };