import { MonadPlus } from './monadplus';
import { AtomicPromise } from '../promise';
import { noop } from '../function';
import { push } from '../array';
export class Maybe extends MonadPlus {
constructor(thunk: () => Maybe) {
super(thunk);
}
// Bug: TypeScript
// @ts-ignore-error
public fmap(f: (a: a) => b): Maybe {
return this.bind(a => new Just(f(a)));
}
// Bug: TypeScript
// @ts-ignore-error
public ap(this: Maybe<(a: a) => z>, a: Maybe): Maybe;
// @ts-ignore-error
public ap(this: Maybe<(a: a, b: b) => z>, a: Maybe): Maybe<(b: b) => z>;
// @ts-ignore-error
public ap(this: Maybe<(a: a, b: b, c: c) => z>, a: Maybe): Maybe<(b: b, c: c) => z>;
// @ts-ignore-error
public ap(this: Maybe<(a: a, b: b, c: c, d: d) => z>, a: Maybe): Maybe<(b: b, c: c, d: d) => z>;
// @ts-ignore-error
public ap(this: Maybe<(a: a, b: b, c: c, d: d, e: e) => z>, a: Maybe): Maybe<(b: b, c: c, d: d, e: e) => z>;
// @ts-ignore-error
public ap(this: Maybe<(...as: any[]) => z>, a: Maybe): Maybe {
return Maybe.ap(this, a);
}
// Bug: TypeScript
// @ts-expect-error
public bind(f: (a: a) => Maybe): Maybe {
return new Maybe(() => {
const m: Maybe = this.evaluate();
switch (m.constructor) {
case Just:
return f(m.extract());
case Nothing:
return m as Nothing;
default:
return m.bind(f);
}
});
}
// Bug: TypeScript
// @ts-expect-error
public join(this: Maybe>): Maybe {
return this.bind(m => m);
}
public guard(cond: boolean): Maybe {
return cond ? this : Maybe.mzero;
}
public extract(): a;
public extract(nothing: () => a): a;
public extract(nothing: () => b): a | b;
public extract(nothing: () => b, just: (a: a) => b): b;
public extract(nothing?: () => b, just?: (a: a) => b): a | b {
return just === undefined
? this.evaluate().extract(nothing!)
: this.fmap(just).extract(nothing!);
}
public static do(block: () => Iterator, Maybe, a>): Maybe {
const iter = block();
let value: a | 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 Maybe {
export declare function fmap(f: (a: a) => b, m: Maybe): Maybe;
export function pure(a: a): Maybe {
return new Just(a);
}
export declare function ap(mf: Maybe<(a: a) => b>, ma: Maybe): Maybe;
export declare function ap(mf: Maybe<(a: a) => b>): (ma: Maybe) => Maybe;
export const Return = pure;
export declare function bind(f: (a: a) => Maybe, m: Maybe): Maybe;
export function sequence(fm: Maybe[]): Maybe;
export function sequence(fm: Maybe>): AtomicPromise>;
export function sequence(fm: Maybe[] | Maybe>): Maybe | AtomicPromise> {
return fm instanceof Maybe
? fm.extract(() => AtomicPromise.resolve(mzero), a => AtomicPromise.resolve(a).then(Return))
: fm.reduce((acc, m) => acc.bind(as => m.fmap(a => push(as, [a]))), Return([]));
}
}
export class Just extends Maybe {
constructor(private readonly value: a) {
super(throwCallError);
}
public override bind(f: (a: a) => Just): Just;
public override bind(f: (a: a) => Nothing): Nothing;
public override bind(f: (a: a) => Maybe): Maybe;
public override bind(f: (a: a) => Maybe): Maybe {
return new Maybe(() => f(this.extract()));
}
public override extract(): a;
public override extract(nothing: () => a): a;
public override extract(nothing: () => b): a;
public override extract(nothing: () => b, just: (a: a) => b): b;
public override extract(nothing?: () => b, just?: (a: a) => b): a | b {
if (just !== undefined) return just(this.value);
return this.value;
assert([nothing]);
}
}
export class Nothing extends Maybe {
constructor() {
super(throwCallError);
}
public override bind(f: (a: never) => Maybe): Nothing {
return this;
assert(f);
}
public override extract(): never;
public override extract(nothing: () => b): b;
public override extract(nothing: () => b, just: (a: never) => b): b;
public override extract(nothing?: () => b): b {
if (nothing !== undefined) return nothing();
throw new Error(`Spica: Maybe: Nothing value is extracted`);
}
}
export namespace Maybe {
export const mzero = new Nothing();
export function mplus(ml: Maybe, mr: Maybe): Maybe {
return new Maybe(() =>
ml.extract(() => mr, () => ml));
}
}
function throwCallError(): never {
throw new Error(`Spica: Maybe: Invalid thunk call`);
}