import { Monad } from './monad'; import { AtomicPromise } from '../promise'; import { noop } from '../function'; import { push } from '../array'; export class Either extends Monad { constructor(thunk: () => Either) { super(thunk); } public fmap(f: (b: b) => c): Either { return this.bind(b => new Right(f(b))); } public ap(this: Either z>, b: Either): Either; public ap(this: Either z>, b: Either): Either z>; public ap(this: Either z>, b: Either): Either z>; public ap(this: Either z>, b: Either): Either z>; public ap(this: Either z>, b: Either): Either z>; public ap(this: Either z>, b: Either): Either { return Either.ap(this, b); } // Bug: TypeScript // @ts-expect-error public bind(f: (b: b) => Either): Either { return new Either(() => { const m: Either = this.evaluate(); switch (m.constructor) { case Right: return f(m.extract()); case Left: return m as Left; default: return m.bind(f); } }); } // Bug: TypeScript // @ts-expect-error public join(this: Either>): Either { return this.bind(m => m); } public extract(): b; public extract(left: (a: a) => b): b; public extract(left: (a: a) => c): b | c; public extract(left: (a: a) => c, right: (b: b) => c): c; public extract(left?: (a: a) => c, right?: (b: b) => c): b | c { return right === undefined ? this.evaluate().extract(left!) : this.fmap(right).extract(left!); } public static do(block: () => Iterator, Either, b>): Either; public static do(block: () => Iterator, Either, b>): Either; public static do(block: () => Iterator, Either, b>): Either { const iter = block(); let value: b | undefined; while (true) { const { value: m, done } = iter.next(value!); if (done) return m; if (m.extract(noop, a => [value = a]) === undefined) return m; } } } export namespace Either { export declare function fmap(f: (b: b) => c, m: Either): Either; export function pure(b: b): Right; export function pure(b: b): Either; export function pure(b: b): Either { return new Right(b); } export declare function ap(mf: Either c>, ma: Either): Either; export declare function ap(mf: Either c>): (ma: Either) => Either; export const Return = pure; export declare function bind(f: (b: b) => Either, m: Either): Either; export function sequence(fm: Either[]): Either; export function sequence(fm: Either>): AtomicPromise>; export function sequence(fm: Either[] | Either>): Either | AtomicPromise> { return fm instanceof Either ? fm.extract(b => AtomicPromise.resolve(new Left(b)), a => AtomicPromise.resolve(a).then>(Return)) : fm.reduce((acc, m) => acc.bind(as => m.fmap(a => push(as, [a]))), Return([])); } } export class Right extends Either { constructor(private readonly value: b) { super(throwCallError); } public override bind(f: (b: b) => Right): Right; public override bind(f: (b: b) => Left): Left; public override bind(f: (b: b) => Either): Either; public override bind(f: (b: b) => Either): Either { return new Either(() => f(this.extract())); } public override extract(): b; public override extract(left: (a: never) => b): b; public override extract(left: (a: never) => c): b; public override extract(left: (a: never) => c, right: (b: b) => c): c; public override extract(left?: (a: never) => c, right?: (b: b) => c): b | c { if (right !== undefined) return right(this.value); return this.value; assert([left]); } } export class Left extends Either { constructor(private value: a) { super(throwCallError); } public override bind(f: (b: never) => Either): Left { return this; assert(f); } public override extract(): never; public override extract(left: (a: a) => c): c; public override extract(left: (a: a) => c, right: (b: never) => c): c; public override extract(left?: (a: a) => c): c { if (left !== undefined) return left(this.value); throw this.value; } } function throwCallError(): never { throw new Error(`Spica: Either: Invalid thunk call`); }