cli-framework/src/lib/util/basic/either.ts

69 lines
1.9 KiB
TypeScript

export type Left<E> = {readonly _tag: 'Left', readonly left: E}
export type Right<A> = {readonly _tag: 'Right', readonly right: A}
export type Either<A, E> = Right<A> | Left<E>
// Constructors
export const left = <E>(e: E): Either<never, E> => ({
_tag: 'Left',
left: e,
})
export const right = <A>(a: A): Either<A, never> => ({
_tag: 'Right',
right: a,
})
// Operations
export const eitherMap =
<A, B, E>(f: (a: A) => B) => (fa: Either<A, E>): Either<B, E> =>
fa._tag === 'Right' ? right(f(fa.right)) : fa
export const eitherChain =
<A, B, E>(f: (a: A) => Either<B, E>) => (fa: Either<A, E>): Either<B, E> =>
fa._tag === 'Right' ? f(fa.right) : fa
export const fold =
<A, E, B>(onLeft: (e: E) => B, onRight: (a: A) => B) =>
(fa: Either<A, E>): B =>
fa._tag === 'Left' ? onLeft(fa.left) : onRight(fa.right)
// Do notation
export class DoEither<T extends Record<string, unknown>, E> {
constructor(private readonly either: Either<T, E>) {}
bind<K extends string, A>(
key: K,
fa: Either<A, E>,
): DoEither<T & Record<K, A>, E> {
const newEither = eitherChain<T, T & Record<K, A>, E>((scope: T) =>
eitherMap<A, T & Record<K, A>, E>((a: A) => ({...scope, [key]: a}))(fa)
)(this.either)
return new DoEither(newEither)
}
return<B>(f: (scope: T) => B): Either<B, E> {
return eitherMap<T, B, E>(f)(this.either)
}
done(): Either<T, E> {
return this.either
}
do(fa: Either<unknown, E>): DoEither<T, E> {
const newEither = eitherChain<T, T, E>((scope: T) =>
eitherMap<unknown, T, E>(() => scope)(fa)
)(this.either)
return new DoEither(newEither)
}
doL(f: (scope: T) => Either<unknown, E>): DoEither<T, E> {
const newEither = eitherChain<T, T, E>((scope: T) =>
eitherMap<unknown, T, E>(() => scope)(f(scope))
)(this.either)
return new DoEither(newEither)
}
static start<E>(): DoEither<{}, E> {
return new DoEither(right({}))
}
}