import {monad, Monad} from "jabz/monad";
import {Consumer, Observer} from "./frp-common";
import {Behavior} from "./behavior";
/**
* A future is a thing that occurs at some point in time with a value.
* It can be understood as a pair consisting of the time the future
* occurs and its associated value. It is quite like a JavaScript
* promise.
*/
@monad
export abstract class Future implements Monad, Consumer {
// Flag indicating whether or not this future has occured.
occured: boolean;
// The value of the future. Often `undefined` until occurence.
value: A;
// The consumers that depends on this producer. These should be
// notified when the producer has a value.
protected listeners: Consumer[];
constructor() {
this.listeners = [];
}
listen(o: Consumer): void {
if (this.occured !== true) {
this.listeners.push(o);
} else {
o.push(this.value);
}
}
subscribe(f: (a: A) => void): void {
new Subscription(f, this);
}
// `push` is called by the parent of a future once it resolves with
// a value.
abstract push(val: any): void;
resolve(val: A): void {
this.occured = true;
this.value = val;
const listeners = this.listeners;
for (let i = 0, l = listeners.length; i < l; ++i) {
listeners[i].push(val);
}
}
// A future is a functor, when the future occurs we can feed its
// result through the mapping function
map(f: (a: A) => B): Future {
return new MapFuture(f, this);
}
mapTo(b: B): Future {
return new MapToFuture(b, this);
}
// A future is an applicative. `of` gives a future that has always
// occured at all points in time.
static of(b: B): Future {
return new PureFuture(b);
}
of(b: B): Future {
return new PureFuture(b);
}
ap: (f: Future<(a: A) => B>) => Future;
lift(f: (t: T1) => R, m: Future): Future;
lift(f: (t: T1, u: T2) => R, m1: Future, m2: Future): Future;
lift(f: (t1: T1, t2: T2, t3: T3) => R, m1: Future, m2: Future, m3: Future): Future;
lift(f: any, ...args: Future[]): any {
return f.length === 1 ? new MapFuture(f, args[0])
: new LiftFuture(f, args);
}
static multi: false;
multi = false;
// A future is a monad. Once the first future occurs `chain` passes
// its value through the chain function and the future it returns is
// the one returned by `chain`.
chain(f: (a: A) => Future): Future {
return new ChainFuture(f, this);
}
flatten: () => Future;
}
class MapFuture extends Future {
constructor(private f: (a: A) => B, private parent: Future) {
super();
parent.listen(this);
}
push(val: any): void {
this.resolve(this.f(val));
}
}
class MapToFuture extends Future {
constructor(public value: A, private parent: Future) {
super();
parent.listen(this);
}
push(_: any): void {
this.resolve(this.value);
}
}
class PureFuture extends Future {
constructor(public value: A) {
super();
this.occured = true;
}
push(_: any): void {
throw new Error("A PureFuture should never be pushed to.");
}
}
class LiftFuture extends Future {
private delivered: number = 0;
private dependencies: number;
constructor(private f: Function, private futures: Future[]) {
super();
const l = this.dependencies = futures.length;
for (let i = 0; i < l; ++i) {
futures[i].listen(this);
}
}
push(_: any): void {
const l = this.dependencies;
if (++this.delivered === l) {
// All the dependencies have occurred.
for (let i = 0; i < l; ++i) {
this.futures[i] = this.futures[i].value;
}
this.resolve(this.f.apply(undefined, this.futures));
}
}
}
class ChainFuture extends Future {
private parentOccurred: boolean = false;
constructor(private f: (a: A) => Future, private parent: Future) {
super();
parent.listen(this);
}
push(val: any): void {
if (this.parentOccurred === false) {
// The first future occured. We can now call `f` with its value
// and listen to the future it returns.
this.parentOccurred = true;
const newFuture = this.f(val);
newFuture.listen(this);
} else {
this.resolve(val);
}
}
}
// A Sink is a producer that one can imperatively resolve.
class FutureSink extends Future {
push(val: any): void {
throw new Error("A sink should never be pushed to.");
}
}
export function sinkFuture(): Future {
return new FutureSink();
}
// A subscription is a consumer that performs a side-effect
class Subscription implements Consumer {
constructor(private f: (a: A) => void, private parent: Future) {
parent.listen(this);
}
push(a: A): void {
this.f(a); // let `f` perform its side-effect.
}
}
export function fromPromise(p: Promise): Future {
const future = sinkFuture();
p.then(future.resolve.bind(future));
return future;
}
/**
* Create a future from a pushing behavior. The future occurs when the
* behavior pushes its next value. Constructing a BehaviorFuture is
* impure and should not be done direcly.
* @private
*/
export class BehaviorFuture extends Future implements Observer {
constructor(private b: Behavior) {
super();
b.addListener(this);
}
endPulling(): void {
throw new Error("Behavior future should never switch to pushing");
}
beginPulling(): void {
throw new Error("Behavior future does not support pushing behavior");
}
push(a: A): void {
this.b.removeListener(this);
this.resolve(a);
}
}