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); } }