Home Reference Source

packages/oo7/src/bond.js

// (C) Copyright 2016-2017 Parity Technologies (UK) Ltd.
//
// 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
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const BondCache = require('./bondCache');

var subscripted = {};
// Any names which should never be subscripted.
const reservedNames = { toJSON: true, toString: true };

function symbolValues (o) {
	return Object.getOwnPropertySymbols(o).map(k => o[k]);
}

function equivalent (a, b) {
	return JSON.stringify(a) === JSON.stringify(b);
}

/**
 * An object which tracks a single, potentially variable, value.
 * {@link Bond}s may be updated to new values with {@link Bond#changed} and reset to an indeterminate
 * ("not ready") value with {@link Bond#reset}.
 *
 * {@link Bond}s track their dependents - aspects of the program, including other {@link Bond}s,
 * which reference their current value. Dependents may be added with {@link Bond#use} and
 * removed with {@link Bond#drop}.
 *
 * A {@link Bond} may be tied to a particular function to ensure it is called whenever
 * the value changes. This implies a dependency, and can be registered with {@link Bond#tie} and
 * dropped with {@link Bond#untie}. A function may also be called should the {@link Bond} be reverted
 * to an undefined value; in this case {@link Bond#notify} and {@link Bond#unnotify} should
 * be used.
 *
 * {@link Bond}s can be made to execute a function once their value becomes ready
 * using {@link Bond#then}, which in some sense replicates the same function in the
 * context of a `Promise`. The similar function {@link Bond#done} is also supplied which
 * executes a given function when the {@link Bond} reaches a value which is considered
 * "final", determined by {@link Bond#isDone} being implemented and `true`. Precisely
 * what any given {@link Bond} considers final depends entirely on the subclass of
 * {@link Bond}; for the {@link Bond} class itself, `isDone` always returns `false` and thus
 * {@link Bond#done} is unusable. The value of the {@link Bond}, once _ready_, may
 * be logged to the console with the {@link Bond#log} function.
 *
 * A {@link Bond} can provide a derivative {@link Bond} whose value reflects the "readiness"
 * of the original, using {@link Bond#ready} and conversely {@link Bond#notReady}. This
 * can also be queried normally with {@link Bond#isReady}.
 *
 * One or a number of {@link Bond}s can be converted into a single {Promise} with the
 * {@link Bond#promise} function.
 *
 * `Bonds` can be composed. {@link Bond#map} creates a new {@link Bond} whose value is a
 * transformation. {@link Bond.all} creates a new {@link Bond} which evaluates to the array
 * of values of each of a number of dependent {@link Bond}s. {@link Bond.mapAll} combines
 * both. {@link Bond#reduce} allows a {@link Bond} that evaluates to array to be
 * transformed into some other value recursively.
 *
 * {@link Bond#sub} forms a derivative {@link Bond} as the subscript (square-bracket
 * indexing). {@link Bond#subscriptable} may be used to return a `Proxy` object that
 * allows the {@link Bond} to be subscripted (square-bracket indexed) directly without
 * need of the {@link Bond#sub} function.
 *
 * {@link Bond} is built to be subclassed. When subclassing, three functions are
 * useful to implement. {@link Bond#isDone} may be implemented
 * in order to make {@link Bond#done} be useful. {@link Bond#initialise} is called exactly once
 * when there becomes at least one dependent; {@link Bond#finalise} is called when there
 * are no longer any dependents.
 *
 * _WARNING_: You should not attempt to use the `toString` function with this
 * class. It cannot be meaningfully converted into a string, and to attempt it
 * will give an undefined result.
 */
class Bond {
	/**
	 * Constructs a new {@link Bond} object whose value is _not ready_.
	 *
	 * @param {boolean} mayBeNull - `true` if this instance's value may ever
	 * validly be `null`. If `false`, then setting this object's value to `null`
	 * is equivalent to reseting back to being _not ready_.
	 */
	constructor (mayBeNull = true, cache = null) {
		// Functions that should execute whenever we resolve to a new, "ready"
		// value. They are passed the new value as a single parameter.
		// Each function is mapped to from a `Symbol`, which can be used to
		// remove it.
		this._subscribers = {};
		// Equivalent to `_subscribers`, except that after executing, the
		// function is removed from this array. No mapping is provided so they
		// cannot be removed except by triggering.
		this._thens = [];
		// Functions that should execute whenever either the resolved value
		// changes, or our readiness changes. No parameters are passed.
		// Each function is mapped to from a `Symbol`, which can be used to
		// remove it.
		this._notifies = {};

		// Are we resolved to a value at all. If `false`, we are not yet
		// resolved to a value and `_value` is meaningless.
		this._ready = false;
		// Our currently resolved value, if any.
		this._value = null;
		// Is the value in the middle of having an update triggered?
		this._triggering = null;

		// Is it valid to resolve to `null`? By default it is value.
		this._mayBeNull = mayBeNull;

		// The reference count of the number of dependents. If zero, then there
		// is no need to go to any effort to track changes. This is used for
		// specialisations where tracking changes requires holding or managing
		// resources.
		// This is never smaller but can be larger than the total number of
		// callbacks registered between `_subscribers`, `_thens` and
		// `_notifies`.
		this._users = 0;

		// The Universally Unique ID, a string used to manage caching and
		// inter-tab result sharing.
		this._uuid = cache ? cache.id : null;
		// A method for stringifying this Bond's result when using with the cache.
		this._stringify = cache ? cache.stringify : null;
		// A method for unstringifying this Bond's result when using with the cache.
		this._parse = cache ? cache.parse : null;
	}

	toString () {
		// Bonds make little sense as strings, and our subscripting trick (where
		// we are able to use Bonds as keys) only works if we can coerce into a
		// string. We store the reverse lookup (symbol -> Bond) in a global
		// table `subscripted` so that it can be retrieved while interpreting
		// the subscript in the code Proxy code found in `subscriptable`.
		let s = Symbol('Bond');
		subscripted[s] = this;
		return s;
	}

	/**
	 * Provides a transparently subscriptable version of this object.
	 *
	 * The object that is returned from this function is a convenience `Proxy`
	 * which acts exactly equivalent
	 * to the original {@link Bond}, except that any subscripting of fields that are
	 * not members of the {@link Bond} object will create a new {@link Bond} that
	 * itself evaluates to this {@link Bond}'s value when subscripted with the same
	 * field.
	 *
	 * @example
	 * let x = (new Bond).subscriptable();
	 * let y = x.foo;
	 * y.log(); // nothing yet
	 * x.changed({foo: 42, bar: 69});	// logs 42
	 *
	 * @param {number} depth - The maximum number of levels of subscripting that
	 * the returned `Proxy` will support.
	 * @returns {Proxy} - `Proxy` object that acts as a subscriptable variation
	 * for convenience.
	 */
	subscriptable (depth = 1) {
		// No subscripting at all if depth is 0.
		// We will recurse if > 1.
		if (depth === 0) { return this; }

		let r = new Proxy(this, {
			// We proxy the get object field:
			get (receiver, name) {
				// Skip the magic proxy and just interpret directly if the field
				// name is a string/number and it's either an extent key in the
				// underlying `Bond` or it's a reserved field name (e.g. toString).
				if (
					(typeof (name) === 'string' || typeof (name) === 'number')				&&
					(reservedNames[name] || typeof (receiver[name]) !== 'undefined')
				) {
					return receiver[name];
				}

				// If it's a symbolic key, then it's probably a `Bond` symbolified
				// in our toString function. Look it up in the global Bond symbol
				// table and recurse into one less depth.
				if (typeof (name) === 'symbol') {
					if (Bond._knowSymbol(name)) {
						return receiver
							.sub(Bond._fromSymbol(name))
							.subscriptable(depth - 1);
					} else {
						//						console.warn(`Unknown symbol given`);
						return null;
					}
				}
				// console.log(`Subscripting: ${JSON.stringify(name)}`)
				// Otherwise fall back with a simple subscript and recurse
				// back with one less depth.
				return receiver.sub(name).subscriptable(depth - 1);
			}
		});
		return r;
	}

	// Check to see if there's a symbolic reference for a Bond.
	static _knowSymbol (name) {
		return !!subscripted[name];
	}
	// Lookup a symbolic Bond reference and remove it from the global table.
	static _fromSymbol (name) {
		let sub = subscripted[name];
		delete subscripted[name];
		return sub;
	}

	/**
	 * Alters this object so that it is always _ready_.
	 *
	 * If this object is ever {@link Bond#reset}, then it will be changed to the
	 * value given.
	 *
	 * @example
	 * let x = (new Bond).defaultTo(42);
	 * x.log();	// 42
	 * x.changed(69);
	 * x.log();	// 69
	 * x.reset();
	 * x.log() // 42
	 *
	 * @param {*} x - The value that this object represents if it would otherwise
	 * be _not ready_.
	 * @returns {@link Bond} - This (mutated) object.
	 */
	defaultTo (_defaultValue) {
		this._defaultTo = _defaultValue;
		if (!this._ready) {
			this.trigger(_defaultValue);
		}
		return this;
	}

	/**
	 * Resets the state of this Bond into being _not ready_.
	 *
	 * Any functions that are registered for _notification_ (see {@link Bond#notify})
	 * will be called if this {@link Bond} is currently _ready_.
	 */
	reset () {
		if (this._defaultTo !== undefined) {
			this.trigger(this._defaultTo);
			return;
		}
		if (this._ready) {
			this._ready = false;
			this._value = null;
			symbolValues(this._notifies).forEach(callback => callback());
		}
	}
	/**
	 * Makes the object _ready_ and sets its current value.
	 *
	 * Any functions that are registered for _notification_ (see {@link Bond#notify})
	 * or are _tied_ (see {@link Bond#tie}) will be called if this {@link Bond} is not
	 * currently _ready_ or is _ready_ but has a different value.
	 *
	 * This function is a no-op if the JSON representations of `v` and of the
	 * current value, if any, are equal.
	 *
	 * @param {*} v - The new value that this object should represent. If `undefined`
	 * then the function does nothing.
	 */
	changed (newValue) {
		if (typeof (newValue) === 'undefined') {
			return;
		}
		//		console.log(`maybe changed (${this._value} -> ${v})`);
		if (!this._mayBeNull && newValue === null) {
			this.reset();
		} else if (!this._ready || !equivalent(newValue, this._value)) {
			this.trigger(newValue);
		}
	}

	/**
	 * Makes the object _ready_ and sets its current value.
	 *
	 * Any functions that are registered for _notification_ (see {@link Bond#notify})
	 * or are _tied_ (see {@link Bond#tie}) will be called if this {@link Bond} is not
	 * currently _ready_ or is _ready_ but has a different value.
	 *
	 * Unlike {@link Bond#changed}, this function doesn't check equivalence
	 * between the new value and the current value.
	 *
	 * @param {*} v - The new value that this object should represent. By default,
	 * it will reissue the current value. It is an error to call it without
	 * an argument if it is not _ready_.
	 */
	trigger (newValue = this._value) {
		// Cannot trigger to an undefined value (just reset it or call with `null`).
		if (typeof (newValue) === 'undefined') {
			console.error(`Trigger called with undefined value`);
			return;
		}
		// Cannot trigger as a recourse to an existing trigger.
		if (this._triggering !== null) {
			console.error(`Trigger cannot be called while already triggering.`, this._triggering.becoming, newValue);
			return;
		}
		this._triggering = { becoming: newValue };

		if (!this._mayBeNull && newValue === null) {
			this.reset();
		} else {
			//			console.log(`firing (${JSON.stringify(v)})`);
			this._ready = true;
			this._value = newValue;
			symbolValues(this._notifies).forEach(callback => callback());
			symbolValues(this._subscribers).forEach(callback => callback(this._value));
			this._thens.forEach(callback => {
				callback(this._value);
				this.drop();
			});
			this._thens = [];
		}

		this._triggering = null;

		if (this._uuid && !this._noCache && Bond.cache) {
			Bond.cache.changed(this._uuid, newValue);
		}
	}

	/**
	 * Register a single dependency for this object.
	 *
	 * Notes that the object's value is in use, and that it should be computed.
	 * {@link Bond} sub-classes are allowed to not work properly unless there is
	 * at least one dependency registered.
	 *
	 * @see {@link Bond#initialise}, {@link Bond#finalise}.
	 */
	use () {
		if (this._users === 0) {
			if (!this._uuid || !!this._noCache || !Bond.cache) {
				this.initialise();
			} else {
				Bond.cache.initialise(this._uuid, this, this._stringify, this._parse);
			}
		}
		this._users++;
		return this;
	}

	/**
	 * Unregister a single dependency for this object.
	 *
	 * Notes that a previously registered dependency has since expired. Must be
	 * called exactly once for each time {@link Bond#use} was called.
	 */
	drop () {
		if (this._users === 0) {
			throw new Error(`mismatched use()/drop(): drop() called once more than expected!`);
		}
		this._users--;
		if (this._users === 0) {
			if (!this._uuid || !!this._noCache || !Bond.cache) {
				this.finalise();
			} else {
				Bond.cache.finalise(this._uuid, this);
			}
		}
	}

	/**
	 * Initialise the object.
	 *
	 * Will be called at most once before an accompanying {@link Bond#finalise}
	 * and should initialise/open/create any resources that are required for the
	 * sub-class to maintain its value.
	 *
	 * @access protected
	 */
	initialise () {}

	/**
	 * Uninitialise the object.
	 *
	 * Will be called at most once after an accompanying {@link Bond#initialise}
	 * and should close/finalise/drop any resources that are required for the
	 * sub-class to maintain its value.
	 *
	 * @access protected
	 */
	finalise () {}

	/**
	 * Returns whether the object is currently in a terminal state.
	 *
	 * _WARNING_: The output of this function should not change outside of a
	 * value change. If it ever changes without the value changing, `trigger`
	 * should be called to force an update.
	 *
	 * @returns {boolean} - `true` when the value should be interpreted as being
	 * in a final state.
	 *
	 * @access protected
	 * @see {@link Bond#done}
	 */
	isDone () { return false; }

	/**
	 * Notification callback.
	 * @callback Bond~notifyCallback
	 */

	/**
	 * Register a function to be called when the value or the _readiness_
	 * changes.
	 *
	 * Calling this function already implies calling {@link Bond#use} - there
	 * is no need to call both.
	 *
	 * Use this only when you need to be notified should the object be reset to
	 * a not _ready_ state. In general you will want to use {@link Bond#tie}
	 * instead.
	 *
	 * @param {Bond~notifyCallback} f - The function to be called. Takes no parameters.
	 * @returns {Symbol} An identifier for this registration. Must be provided
	 * to {@link Bond#unnotify} when the function no longer needs to be called.
	 */
	notify (callback) {
		this.use();
		let id = Symbol('notify::id');
		this._notifies[id] = callback;
		if (this._ready) {
			callback();
		}
		return id;
	}

	/**
	 * Unregister a function previously registered with {@link Bond#notify}.
	 *
	 * Calling this function already implies calling {@link Bond#drop} - there
	 * is no need to call both.
	 *
	 * @param {Symbol} id - The identifier returned from the corresponding
	 * {@link Bond#notify} call.
	 */
	unnotify (id) {
		delete this._notifies[id];
		this.drop();
	}

	/**
	 * Tie callback.
	 * @callback Bond~tieCallback
	 * @param {&} value - The current value to which the object just changed.
	 * @param {Symbol} id - The identifier of the registration for this callback.
	 */

	/**
	 * Register a function to be called when the value changes.
	 *
	 * Calling this function already implies calling {@link Bond#use} - there
	 * is no need to call both.
	 *
	 * Unlike {@link Bond#notify}, this does not get
	 * called should the object become reset into being not _ready_.
	 *
	 * @param {Bond~tieCallback} f - The function to be called.
	 * @returns {Symbol} - An identifier for this registration. Must be provided
	 * to {@link Bond#untie} when the function no longer needs to be called.
	 */
	tie (callback) {
		this.use();
		let id = Symbol('tie::id');
		this._subscribers[id] = callback;
		if (this._ready) {
			callback(this._value, id);
		}
		return id;
	}

	/**
	 * Unregister a function previously registered with {@link Bond#tie}.
	 *
	 * Calling this function already implies calling {@link Bond#drop} - there
	 * is no need to call both.
	 *
	 * @param {Symbol} id - The identifier returned from the corresponding
	 * {@link Bond#tie} call.
	 */
	untie (id) {
		delete this._subscribers[id];
		this.drop();
	}

	/**
	 * Determine if there is a definite value that this object represents at
	 * present.
	 *
	 * @returns {boolean} - `true` if there is presently a value that this object represents.
	 */
	isReady () { return this._ready; }

	/**
	 * Provide a derivative {@link Bond} which represents the same as this object
	 * except that before it is ready it evaluates to a given default value and
	 * after it becomes ready for the first time it stays fixed to that value
	 * indefinitely.
	 *
	 * @param {Symbol} defaultValue - The value that the new bond should take when
	 * this bond is not ready.
	 * @returns {@link Bond} - Object representing the value returned by
	 * this {@link Bond} except that it evaluates to the given default value when
	 * this bond is not ready and sticks to the first value that made it ready.
	 */
	latched (defaultValue = undefined, mayBeNull = undefined, cache = null) {
		const LatchBond = require('./latchBond');

		return new LatchBond(
			this,
			typeof defaultValue === 'undefined' ? undefined : defaultValue,
			typeof mayBeNull === 'undefined' ? undefined : mayBeNull,
			cache
		);
	}

	/**
	 * Provide a {@link Bond} which represents the same as this object except that
	 * it takes a particular value when this would be unready.
	 *
	 * @param {Symbol} defaultValue - The value that the new bond should take when
	 * this bond is not ready.
	 * @returns {@link Bond} - Object representing the value returned by
	 * this {@link Bond} except that it evaluates to the given default value when
	 * this bond is not ready. The returned object itself is always _ready_.
	 */
	default (defaultValue = null) {
		const DefaultBond = require('./defaultBond');

		return new DefaultBond(defaultValue, this);
	}

	/**
	 * Provide a {@link Bond} which represents whether this object itself represents
	 * a particular value.
	 *
	 * @returns {@link Bond} - Object representing the value returned by
	 * this {@link Bond}'s {@link Bond#isReady} result. The returned object is
	 * itself always _ready_.
	 */
	ready () {
		const ReadyBond = require('./readyBond');

		if (!this._readyBond) {
			this._readyBond = new ReadyBond(this);
		}
		return this._readyBond;
	}

	/**
	 * Convenience function for the logical negation of {@link Bond#ready}.
	 *
	 * @example
	 * // These two expressions are exactly equivalent:
	 * bond.notReady();
	 * bond.ready().map(_ => !_);
	 *
	 * @returns {@link Bond} Object representing the logical opposite
	 * of the value returned by
	 * this {@link Bond}'s {@link Bond#isReady} result. The returned object is
	 * itself always _ready_.
	 */
	notReady () {
		const NotReadyBond = require('./notReadyBond');

		if (!this._notReadyBond) {
			this._notReadyBond = new NotReadyBond(this);
		}
		return this._notReadyBond;
	}

	/**
	 * Then callback.
	 * @callback Bond~thenCallback
	 * @param {*} value - The current value to which the object just changed.
	 */

	/**
	 * Register a function to be called when this object becomes _ready_.
	 *
	 * For an object to be considered _ready_, it must represent a definite
	 * value. In this case, {@link Bond#isReady} will return `true`.
	 *
	 * If the object is already _ready_, then `f` will be called immediately. If
	 * not, `f` will be deferred until the object assumes a value. `f` will be
	 * called at most once.
	 *
	 * @param {Bond~thenCallback} f The callback to be made once the object is ready.
	 *
	 * @example
	 * let x = new Bond;
	 * x.then(console.log);
	 * x.changed(42); // 42 is written to the console.
	 */
	then (callback) {
		this.use();
		if (this._ready) {
			callback(this._value);
			this.drop();
		} else {
			this._thens.push(callback);
		}
		return this;
	}

	/**
	 * Register a function to be called when this object becomes _done_.
	 *
	 * For an object to be considered `done`, it must be _ready_ and the
	 * function {@link Bond#isDone} should exist and return `true`.
	 *
	 * If the object is already _done_, then `f` will be called immediately. If
	 * not, `f` will be deferred until the object assumes a value. `f` will be
	 * called at most once.
	 *
	 * @param {Bond~thenCallback} f The callback to be made once the object is ready.
	 *
	 * @example
	 * let x = new Bond;
	 * x.then(console.log);
	 * x.changed(42); // 42 is written to the console.
	 */
	done (callback) {
		if (this.isDone === undefined) {
			throw new Error('Cannot call done() on Bond that has no implementation of isDone.');
		}
		var id;
		let cleanupCallback = newValue => {
			if (this.isDone(newValue)) {
				callback(newValue);
				this.untie(id);
			}
		};
		id = this.tie(cleanupCallback);
		return this;
	}

	/**
	 * Logs the current value to the console.
	 *
	 * @returns {@link Bond} The current object.
	 */
	log () { this.then(console.log); return this; }

	/**
	 * Maps the represented value to a string.
	 *
	 * @returns {@link Bond} A new {link Bond} which represents the `toString`
	 * function on whatever value this {@link Bond} represents.
	 */
	mapToString () {
		return this.map(_ => _.toString());
	}

	/**
	 * Make a new {@link Bond} which is the functional transformation of this object.
	 *
	 * @example
	 * let b = new Bond;
	 * let t = b.map(_ => _ * 2);
	 * t.tie(console.log);
	 * b.changed(21); // logs 42
	 * b.changed(34.5); // logs 69
	 *
	 * @example
	 * let b = new Bond;
	 * let t = b.map(_ => { let r = new Bond; r.changed(_ * 2); return r; });
	 * t.tie(console.log);
	 * b.changed(21); // logs 42
	 * b.changed(34.5); // logs 69
	 *
	 * @example
	 * let b = new Bond;
	 * let t = b.map(_ => { let r = new Bond; r.changed(_ * 2); return [r]; }, 1);
	 * t.tie(console.log);
	 * b.changed(21); // logs [42]
	 * b.changed(34.5); // logs [69]
	 *
	 * @param {function} transform - The transformation to apply to the value represented
	 * by this {@link Bond}.
	 * @param {number} outResolveDepth - The number of levels deep in any array
	 * object values of the result of the transformation that {@link Bond} values
	 * will be resolved.
	 * @default 3
	 * @param {*} cache - Cache information. See constructor.
	 * @default null
	 * @param {*} latched - Should the value be latched so that once ready it stays ready?
	 * @default false
	 * @param {*} mayBeNull - Should the value be allowed to be `null` such that if it ever becomes
	 * null, it is treated as being unready?
	 * @default true
	 * @returns {@link Bond} - An object representing this object's value with
	 * the function `transform` applied to it.
	 */
	map (transform, outResolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
		const TransformBond = require('./transformBond');
		return new TransformBond(transform, [this], [], outResolveDepth, 3, cache, latched, mayBeNull);
	}

	/**
	 * Just like `map`, except that it defaults to no latching and mayBeNull.
	 * @param {function} transform - The transformation to apply to the value represented
	 * by this {@link Bond}.
	 * @param {number} outResolveDepth - The number of levels deep in any array
	 * object values of the result of the transformation that {@link Bond} values
	 * will be resolved.
	 * @default 3
	 * @param {*} cache - Cache information. See constructor.
	 * @default null
	 * @param {*} latched - Should the value be latched so that once ready it stays ready?
	 * @default true
	 * @param {*} mayBeNull - Should the value be allowed to be `null` such that if it ever becomes
	 * null, it is treated as being unready?
	 * @default false
	 * @returns {@link Bond} - An object representing this object's value with
	 * the function `transform` applied to it.
	 */
	xform (transform, outResolveDepth = 3, cache = undefined, latched = true, mayBeNull = false) {
		const TransformBond = require('./transformBond');
		return new TransformBond(transform, [this], [], outResolveDepth, 3, cache, latched, mayBeNull);
	}

	/**
	 * Create a new {@link Bond} which represents this object's array value with
	 * its elements transformed by a function.
	 *
	 * @example
	 * let b = new Bond;
	 * let t = b.mapEach(_ => _ * 2);
	 * t.tie(console.log);
	 * b.changed([1, 2, 3]); // logs [2, 4, 6]
	 * b.changed([21]); // logs [42]
	 *
	 * @param {function} transform - The transformation to apply to each element.
	 * @returns The new {@link Bond} object representing the element-wise
	 * Transformation.
	 */
	mapEach (transform, cache = undefined, latched = false, mayBeNull = true) {
		return this.map(item => item.map(transform), 3, cache, latched, mayBeNull);
	}

	/**
	 * Create a new {@link Bond} which represents this object's value when
	 * subscripted.
	 *
	 * @example
	 * let b = new Bond;
	 * let t = b.sub('foo');
	 * t.tie(console.log);
	 * b.changed({foo: 42}); // logs 42
	 * b.changed({foo: 69}); // logs 69
	 *
	 * @example
	 * let b = new Bond;
	 * let c = new Bond;
	 * let t = b.sub(c);
	 * t.tie(console.log);
	 * b.changed([42, 4, 2]);
	 * c.changed(0); // logs 42
	 * c.changed(1); // logs 4
	 * b.changed([68, 69, 70]); // logs 69
	 *
	 * @param {string|number} name - The field or index by which to subscript this object's
	 * represented value. May itself be a {@link Bond}, in which case, the
	 * resolved value is used.
	 * @param {number} outResolveDepth - The depth in any returned structure
	 * that a {@link Bond} may be for it to be resolved.
	 * @returns {@link Bond} - The object representing the value which is the
	 * value represented by this object subscripted by the value represented by
	 * `name`.
	 */
	sub (name, outResolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
		const TransformBond = require('./transformBond');
		return new TransformBond(
			(object, field) => object[field],
			[this, name],
			[],
			outResolveDepth,
			3,
			cache
		);
	}

	/**
	 * Create a new {@link Bond} which represents the array of many objects'
	 * representative values.
	 *
	 * This object will be _ready_ if and only if all objects in `list` are
	 * themselves _ready_.
	 *
	 * @example
	 * let b = new Bond;
	 * let c = new Bond;
	 * let t = Bond.all([b, c]);
	 * t.tie(console.log);
	 * b.changed(42);
	 * c.changed(69); // logs [42, 69]
	 * b.changed(3); // logs [3, 69]
	 *
	 * @example
	 * let b = new Bond;
	 * let c = new Bond;
	 * let t = Bond.all(['a', {b, c}, 'd'], 2);
	 * t.tie(console.log);
	 * b.changed(42);
	 * c.changed(69); // logs ['a', {b: 42, c: 69}, 'd']
	 * b.changed(null); // logs ['a', {b: null, c: 69}, 'd']
	 *
	 * @param {array} list - An array of {@link Bond} objects, plain values or
	 * structures (arrays/objects) which contain either of these.
	 * @param {number} resolveDepth - The depth in a structure (array or object)
	 * that a {@link Bond} may be in any of `list`'s items for it to be resolved.
	 * @returns {@link Bond} - The object representing the value of the array of
	 * each object's representative value in `list`.
	 */
	static all (list, resolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
		const TransformBond = require('./transformBond');
		return new TransformBond((...args) => args, list, [], 3, resolveDepth, cache, latched, mayBeNull);
	}

	/**
	 * Create a new {@link Bond} which represents a functional transformation of
	 * many objects' representative values.
	 *
	 * @example
	 * let b = new Bond;
	 * b.changed(23);
	 * let c = new Bond;
	 * c.changed(3);
	 * let multiply = (x, y) => x * y;
	 * // These two are exactly equivalent:
	 * let bc = Bond.all([b, c]).map(([b, c]) => multiply(b, c));
	 * let bc2 = Bond.mapAll([b, c], multiply);
	 *
	 * @param {array} list - An array of {@link Bond} objects or plain values.
	 * @param {function} f - A function which accepts as many parameters are there
	 * values in `list` and transforms it into a {@link Bond}, {@link Promise}
	 * or other value.
	 * @param {number} resolveDepth - The depth in a structure (array or object)
	 * that a {@link Bond} may be in any of `list`'s items for it to be resolved.
	 * @param {number} outResolveDepth - The depth in any returned structure
	 * that a {@link Bond} may be for it to be resolved.
	 */
	static mapAll (list, transform, outResolveDepth = 3, resolveDepth = 3, cache = undefined, latched = false, mayBeNull = true) {
		const TransformBond = require('./transformBond');
		return new TransformBond(transform, list, [], outResolveDepth, resolveDepth, cache, latched, mayBeNull);
	}

	// Takes a Bond which evaluates to a = [a[0], a[1], ...]
	// Returns Bond which evaluates to:
	// null iff a.length === 0
	// f(i, a[0])[0] iff f(i, a[0])[1] === true
	// fold(f(0, a[0]), a.mid(1)) otherwise
	/**
	 * Lazily transforms the contents of this object's value when it is an array.
	 *
	 * This operates on a {@link Bond} which should represent an array. It
	 * transforms this into a value based on a number of elements at the
	 * beginning of that array using a recursive _reduce_ algorithm.
	 *
	 * The reduce algorithm works around an accumulator model. It begins with
	 * the `init` value, and incremenetally accumulates
	 * elements from the array by changing its value to one returned from the
	 * `accum` function, when passed the current accumulator and the next value
	 * from the array. The `accum` function may return a {@link Bond}, in which case it
	 * will be resolved (using {@link Bond#then}) and that value used.
	 *
	 * The `accum` function returns a value (or a {@link Bond} which resolves to a value)
	 * of an array with exactly two elements; the first is the new value for the
	 * accumulator. The second is a boolean _early exit_ flag.
	 *
	 * Accumulation will continue until either there are no more elements in the
	 * array to be processed, or until the _early exit_ flag is true, which ever
	 * happens first.
	 *
	 * @param {function} accum - The reduce's accumulator function.
	 * @param {*} init - The initialisation value for the reduce algorithm.
	 * @returns {Bond} - A {@link Bond} representing `init` when the input array is empty,
	 * otherwise the reduction of that array.
	 */
	reduce (accum, init, cache = undefined, latched = false, mayBeNull = true) {
		var nextItem = function (acc, rest) {
			let next = rest.pop();
			return accum(acc, next).map(([result, finished]) =>
				finished
					? result
					: rest.length > 0
						? nextItem(result, rest)
						: null
			);
		};
		return this.map(array => array.length > 0 ? nextItem(init, array) : init, 3, cache, latched, mayBeNull);
	}

	/**
	 * Create a Promise which represents one or more {@link Bond}s.
	 *
	 * @example
	 * let b = new Bond;
 	 * let p = Bond.promise([b, 42])
	 * p.then(console.log);
	 * b.changed(69); // logs [69, 42]
	 * b.changed(42); // nothing.
	 *
	 * @param {array} list - A list of values, {Promise}s or {@link Bond}s.
	 * @returns {Promise} - A object which resolves to an array of values
	 * corresponding to those passed in `list`.
	 */
	static promise (list) {
		return new Promise((resolve, reject) => {
			var finished = 0;
			var resolved = [];
			resolved.length = list.length;

			let done = (index, value) => {
				//				console.log(`done ${i} ${v}`);
				resolved[index] = value;
				finished++;
				//				console.log(`finished ${finished}; l.length ${l.length}`);
				if (finished === resolved.length) {
					//					console.log(`resolving with ${l}`);
					resolve(resolved);
				}
			};

			list.forEach((unresolvedObject, index) => {
				if (Bond.instanceOf(unresolvedObject)) {
					// unresolvedObject is a Bond.
					unresolvedObject.then(value => done(index, value));
				} else if (unresolvedObject instanceof Promise) {
					// unresolvedObject is a Promise.
					unresolvedObject.then(value => done(index, value), reject);
				} else {
					// unresolvedObject is actually just a normal value.
					done(index, unresolvedObject);
				}
			});
		});
	}

	/**
	 * Duck-typed alternative to `instanceof Bond`, when multiple instantiations
	 * of `Bond` may be available.
	 */
	static instanceOf (b) {
		return (
			typeof (b) === 'object' &&
			b !== null &&
			typeof (b.reset) === 'function' &&
			typeof (b.changed) === 'function'
		);
	}
}

Bond.backupStorage = {};
Bond.cache = new BondCache(Bond.backupStorage);

module.exports = Bond;