import { compose } from '../util'; import { left, right, Left, Either } from './Either'; import { Monad } from './Monad'; import { Functor } from '../data/Functor'; /** * free wraps a value in a free */ export const free = (a: A) => new Return(a); /** * suspend lifts a function into a Free monad to mimic tail call recursion. */ export const suspend = (f: () => A) => new Suspend(compose(free, f)); /** * liftF lifts a Functor into a Free. */ export const liftF = , A>(f: F) => new Suspend(f.map(free)); /** * Free is a Free monad that also implements a Free Applicative (almost). * * Inspired by https://cwmyers.github.io/monet.js/#free */ export abstract class Free implements Monad { static free = free; static suspend = suspend; static liftF = liftF; /** * of */ of(a: A): Free { return new Return(a); } /** * map */ map(f: (a: A) => B): Free { return this.chain((a: A) => free(f(a))); } /** * chain */ chain(g: (a: A) => Free): Free { if (this instanceof Suspend) { let f = this.f; return (typeof f === 'function') ? new Suspend((x: A) => f(x).chain(g)) : new Suspend(f.map((free: Free) => free.chain(g))); } else if (this instanceof Return) { g(this.a); } } /** * resume the next stage of the computation */ resume(): Either { if (this instanceof Suspend) { return left(this.f) } else if (this instanceof Return) { return right(this.a); } } /** * hoist hoist(func: (fb: Functor) => Functor): Free { if (this instanceof Suspend) { return new Suspend((func(this.f)) .map((fr: Free) => fr.hoist(func))) } else { return this; } } */ /** * cata */ cata(f: (f: F) => B, g: (a: A) => B): B { return this.resume().cata(f, g); } /** * go runs the computation to completion using f to extract each stage. * @summmary go :: Free, A> → (F> → Free) → A */ go(f: (next: F) => Free): A { if (this instanceof Suspend) { let r = this.resume(); while (r instanceof Left) r = (f(r.takeLeft())).resume(); return r.takeRight(); } else if (this instanceof Return) { return this.a; } } /** * run the Free chain to completion * @summary run :: Free → A */ run(): A { return this.go((next: F): Free => (next)()); } } export class Suspend extends Free { f: F; constructor(f: F) { super(); this.f = f; } } export class Return extends Free { a: A; constructor(a: A) { super(); this.a = a; } }