pragmatic-fp-ts
Version:
Opinionated functional programming library with easy use in mind
153 lines (139 loc) • 4.06 kB
text/typescript
import { Effect, identity, isNil, getValue, Mappable, Predicate } from "./main.ts";
import { Monad, MonadType, NilError, InvalidValueError } from "./types.ts";
export type Either<R, L = Error> = Left<R, L> | Right<R, L>;
export type EitherMatcher<R, L, B> = {
left?: (err: L) => B;
right?: (val: R) => B;
};
export class Left<R, L = Error> extends Monad<R> {
readonly errorValue: L;
constructor(errVal: L) {
super();
this.errorValue = errVal;
}
_<B>(_: Mappable<R, B>, _hint?: L): Either<NonNullable<B>, L> {
return this as any;
}
bind = this._;
bindM<B>(_: Mappable<Monad<R>, Monad<B>>): Either<NonNullable<B>, L> {
return this as any;
}
filter(_: any, _hint?: L): Either<NonNullable<R>, L> {
return this as any;
}
effect(_: any): Either<NonNullable<R>, L> {
return this as any;
}
getValue(): NonNullable<R> {
throw new Error(`Can not get value of Left(${String(this.errorValue)})`);
}
getValueOr(alt: R): R {
return alt;
}
match<B>(matcher: EitherMatcher<R, L, B>): Either<NonNullable<B>, L | Error> {
return matcher.left ? either<B, L>(matcher.left(this.errorValue)) : (this as any);
}
isLeft() {
return true;
}
isRight() {
return false;
}
getReason() {
return this.errorValue;
}
}
export class Right<R extends NonNullable<any>, L = Error> extends Monad<R> {
readonly value: R;
constructor(value: R) {
super();
this.value = value;
}
_<B>(fn: Mappable<R, B>, hint?: L): Either<NonNullable<B>, L | Error> {
try {
const result = fn(this.value);
const bound = either(getValue(result));
return bound.isLeft() && hint ? left(hint) : bound;
} catch (err) {
return left<NonNullable<B>, Error>(err as Error);
}
}
bind = this._;
bindM<B>(fn: Mappable<Monad<R>, Monad<B>>): Either<NonNullable<B>, L | Error> {
try {
const result = fn(this);
return either(getValue(result));
} catch (err) {
return left<NonNullable<B>, L>(err as unknown as L);
}
}
filter(fn: Predicate<R>, hint?: L): Either<NonNullable<R>, L | Error> {
try {
return fn(this.value)
? (this as any)
: left<NonNullable<R>>(hint instanceof Error ? hint : new InvalidValueError(hint as any));
} catch (err) {
return left<NonNullable<R>>(
new Error(`Exception while filtering: ${(err as Error).message}`)
);
}
}
effect(fn: Effect<R>): Either<NonNullable<R>, L> {
try {
fn(this.value);
} catch (err) {}
return this as any;
}
getValue(): NonNullable<R> {
return this.value as NonNullable<R>;
}
getValueOr(_: R): R {
return this.value;
}
match<B>(matcher: EitherMatcher<R, L, B>): Either<NonNullable<B>, L> {
const m = matcher.right ?? (identity as any);
return either(m(this.value)) as any;
}
isLeft() {
return false;
}
isRight() {
return true;
}
getReason() {
throw new Error("Can not get error reason for Right");
}
}
export function left<R, L = Error>(errVal: L) {
return new Left<R, L>(errVal);
}
export function right<R, L = Error>(value: R) {
return new Right<R, L>(value);
}
export function either<R, L = Error>(
value: MonadType<R>,
errVal?: L
): Either<NonNullable<R>, L | Error> {
const innerValue = getValue(value);
return isNil(innerValue)
? errVal
? new Left<NonNullable<R>, L>(errVal)
: new Left<NonNullable<R>, Error>(new NilError())
: new Right<NonNullable<R>, L>(innerValue as any);
}
export function isLeft<R = any, L = Error>(el: unknown): el is Left<R, L> {
return el instanceof Left;
}
export function isRight<R = any, L = Error>(el: unknown): el is Right<R, L> {
return el instanceof Right;
}
export function isEither<R = any, L = Error>(el: unknown): el is Either<R, L> {
return isLeft<R>(el) || isRight<L>(el);
}
export const throwLeftAsError = {
right: (data: any) => data,
left: (reason: Error | string) => {
throw reason instanceof Error ? reason : new Error(reason);
return reason as any;
},
};