import { AtomicPromise } from './promise'; import { curry } from './curry'; import { push } from './array'; export interface Either { fmap(f: (b: b) => c): Either; ap(this: Either z>, b: Either): Either; ap(this: Either z>, b: Either): Either z>; ap(this: Either z>, b: Either): Either z>; ap(this: Either z>, b: Either): Either z>; ap(this: Either z>, b: Either): Either z>; bind(f: (b: b) => Either): Either; join(this: Either>): Either; extract(): b; extract(left: (a: a) => b): b; extract(left: (a: a) => c): b | c; extract(left: (a: a) => c, right: (b: b) => c): c; } class Right implements Either { constructor( private readonly value: b, ) { } public fmap(f: (b: b) => c): Right { return new Right(f(this.value)); } 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); } public bind(f: (b: b) => Left): Left; public bind(f: (b: b) => Right): Right; public bind(f: (b: b) => Either): Either; public bind(f: (b: b) => Either): Either { return f(this.extract()); } public join(this: Right>): Either { return this.value; } public extract(): b; public extract(left: (a: never) => b): b; public extract(left: (a: never) => c): b; public extract(left: (a: never) => c, right: (b: b) => c): c; public extract(left?: (a: never) => c, right?: (b: b) => c): b | c { if (right !== undefined) return right(this.value); return this.value; assert([left]); } } class Left implements Either { constructor( private readonly value: a, ) { } public fmap(f: (b: never) => c): Left { return this; assert(f); } 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>, _: Either): Either { return this as Left; } public bind(f: (b: never) => Either): Left { return this; assert(f); } public join(this: Either>): Left { return this as Left; } public extract(): never; public extract(left: (a: a) => c): c; public extract(left: (a: a) => c, right: (b: never) => c): c; public extract(left?: (a: a) => c): c { if (left !== undefined) return left(this.value); throw this.value; } } type right = Right; function right(b: b): Right; function right(b: b): Either; function right(b: b): Either { return new Right(b); } type left = Left; function left(value: a): Left { return new Left(value); } export { right as Right, left as Left, }; export namespace Either { export function fmap(f: (b: b) => c, m: Either): Either { return m.fmap(f); } export const pure = right; export function ap(mf: Either c>, ma: Either): Either; export function ap(mf: Either c>): (ma: Either) => Either; export function ap(af: Either c>, aa?: Either): Either | ((aa: Either) => Either) { return aa ? af.bind(f => aa.fmap(curry(f))) : (aa: Either) => ap(af, aa); } export const Return = pure; export function bind(f: (b: b) => Either, m: Either): Either { return m.bind(f); } export function sequence(fm: Either[]): Either; export function sequence(fm: Either>): AtomicPromise>; export function sequence(fm: Either[] | Either>): Either | AtomicPromise> { return Array.isArray(fm) ? fm.reduce((acc, m) => acc.bind(as => m.fmap(a => push(as, [a]))), Return([])) : fm.extract(b => AtomicPromise.resolve(new Left(b)), a => AtomicPromise.resolve(a).then>(Return)); } }