/** * This file contains the Either algebraic data type. Either is used to * represent two exclusive types. Generally, Either is used to represent either * a successful computation or a failed computation, with the result of the * failed computation being kept in Left. * * @module Either * @since 2.0.0 */ import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Bimappable } from "./bimappable.ts"; import type { Combinable } from "./combinable.ts"; import type { Comparable } from "./comparable.ts"; import type { Failable, Tap } from "./failable.ts"; import type { Filterable } from "./filterable.ts"; import type { Bind, Flatmappable } from "./flatmappable.ts"; import type { Foldable } from "./foldable.ts"; import type { Initializable } from "./initializable.ts"; import type { BindTo, Mappable } from "./mappable.ts"; import type { Pair } from "./pair.ts"; import type { Predicate } from "./predicate.ts"; import type { Refinement } from "./refinement.ts"; import type { Showable } from "./showable.ts"; import type { Sortable } from "./sortable.ts"; import type { Traversable } from "./traversable.ts"; import type { Wrappable } from "./wrappable.ts"; import * as O from "./option.ts"; import { createTap } from "./failable.ts"; import { createBind } from "./flatmappable.ts"; import { createBindTo } from "./mappable.ts"; import { isNotNil } from "./nil.ts"; import { fromCompare } from "./comparable.ts"; import { fromSort } from "./sortable.ts"; import { flow, pipe } from "./fn.ts"; /** * @since 2.0.0 */ export type Left = { tag: "Left"; left: L }; /** * @since 2.0.0 */ export type Right = { tag: "Right"; right: R }; /** * @since 2.0.0 */ export type Either = Left | Right; /** * @since 2.0.0 */ export interface KindEither extends Kind { readonly kind: Either, Out>; } /** * @since 2.0.0 */ export interface KindRightEither extends Kind { readonly kind: Either>; } /** * @since 2.0.0 */ export function left(left: E): Either { return ({ tag: "Left", left }); } /** * @since 2.0.0 */ export function right(right: A): Either { return ({ tag: "Right", right }); } /** * @since 2.0.0 */ export function wrap(a: A): Either { return right(a); } /** * @since 2.0.0 */ export function fail(b: B): Either { return left(b); } /** * @since 2.0.0 */ export function fromNullable( fe: () => E, ): (value: A) => Either> { return (a) => (isNotNil(a) ? right(a) : left(fe())); } /** * @since 2.0.0 */ export function tryCatch( fn: (...as: AS) => A, onError: (e: unknown) => E, ): (...as: AS) => Either { return (...as: AS) => { try { return right(fn(...as)); } catch (e) { return left(onError(e)); } }; } /** * @since 2.0.0 */ export function fromPredicate( refinement: Refinement, ): (a: A) => Either; export function fromPredicate( predicate: Predicate, ): (a: A) => Either; export function fromPredicate( predicate: Predicate, ): (a: A) => Either { return (a: A) => predicate(a) ? right(a) : left(a); } /** * @since 2.0.0 */ export function match( onLeft: (left: L) => B, onRight: (right: R) => B, ): (ta: Either) => B { return (ta) => isLeft(ta) ? onLeft(ta.left) : onRight(ta.right); } /** * @since 2.0.0 */ export function getOrElse(onLeft: (e: E) => A): (ua: Either) => A { return (ua) => isLeft(ua) ? onLeft(ua.left) : ua.right; } /** * @since 2.0.0 */ export function getRight(ma: Either): O.Option { return pipe(ma, match(O.constNone, O.some)); } /** * @since 2.0.0 */ export function getLeft(ma: Either): O.Option { return pipe(ma, match(O.some, O.constNone)); } /** * @since 2.0.0 */ export function isLeft(m: Either): m is Left { return m.tag === "Left"; } /** * @since 2.0.0 */ export function isRight(m: Either): m is Right { return m.tag === "Right"; } /** * @since 2.0.0 */ export function bimap( fbj: (b: B) => J, fai: (a: A) => I, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? left(fbj(ta.left)) : right(fai(ta.right)); } /** * @since 2.0.0 */ export function swap(ma: Either): Either { return isLeft(ma) ? right(ma.left) : left(ma.right); } /** * @since 2.0.0 */ export function map( fai: (a: A) => I, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? ta : right(fai(ta.right)); } /** * @since 2.0.0 */ export function mapSecond( fbj: (b: B) => J, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? left(fbj(ta.left)) : ta; } /** * @since 2.0.0 */ export function apply( ua: Either, ): (ufai: Either I>) => Either { return (ufai) => isLeft(ua) ? ua : isLeft(ufai) ? ufai : right(ufai.right(ua.right)); } /** * @since 2.0.0 */ export function flatmap( fati: (a: A) => Either, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? ta : fati(ta.right); } /** * @since 2.0.0 */ export function flatmapFirst( faui: (a: A) => Either, ): (ta: Either) => Either { return (ua) => { if (isLeft(ua)) { return ua; } else { const ui = faui(ua.right); return isLeft(ui) ? ui : ua; } }; } /** * @since 2.0.0 */ export function recover( fbui: (b: B) => Either, ): (ua: Either) => Either { return (ua) => isRight(ua) ? ua : fbui(ua.left); } /** * @since 2.0.0 */ export function alt( tb: Either, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? tb : ta; } /** * @since 2.0.0 */ export function fold( foao: (o: O, a: A) => O, o: O, ): (ta: Either) => O { return (ta) => isLeft(ta) ? o : foao(o, ta.right); } /** * @since 2.0.0 */ export function traverse( A: Applicable & Mappable & Wrappable, ): ( faui: (a: A) => $, ) => (ta: Either) => $, J, K], [L], [M]> { //deno-lint-ignore no-explicit-any const onLeft: any = flow(left, A.wrap); //deno-lint-ignore no-explicit-any const mapRight: any = A.map(right); return (faui) => match(onLeft, flow(faui, mapRight)); } /** * @since 2.0.0 */ export function getShowableEither( SB: Showable, SA: Showable, ): Showable> { return ({ show: match( (left) => `Left(${SB.show(left)})`, (right) => `Right(${SA.show(right)})`, ), }); } /** * @since 2.0.0 */ export function getComparableEither( SB: Comparable, SA: Comparable, ): Comparable> { return fromCompare((second) => (first) => { if (isLeft(first)) { if (isLeft(second)) { return SB.compare(second.left)(first.left); } return false; } if (isLeft(second)) { return false; } return SA.compare(second.right)(first.right); }); } /** * @since 2.0.0 */ export function getSortableEither( OB: Sortable, OA: Sortable, ): Sortable> { return fromSort((fst, snd) => isLeft(fst) ? isLeft(snd) ? OB.sort(fst.left, snd.left) : -1 : isLeft(snd) ? 1 : OA.sort(fst.right, snd.right) ); } /** * @since 2.0.0 */ export function getCombinableEither( CA: Combinable, CB: Combinable, ): Combinable> { return { combine: (second) => (first) => { if (isLeft(first)) { if (isLeft(second)) { return left(CB.combine(second.left)(first.left)); } return first; } else if (isLeft(second)) { return second; } return right(CA.combine(second.right)(first.right)); }, }; } /** * @since 2.0.0 */ export function getInitializableEither( CA: Initializable, CB: Initializable, ): Initializable> { return { init: () => right(CA.init()), ...getCombinableEither(CA, CB), }; } /** * @since 2.0.0 */ export function getFlatmappableRight( { combine }: Combinable, ): Flatmappable> { return ({ wrap, apply: (ua) => (ufai) => isLeft(ufai) ? (isLeft(ua) ? left(combine(ua.left)(ufai.left)) : ufai) : (isLeft(ua) ? ua : right(ufai.right(ua.right))), map, flatmap, }); } /** * @since 2.0.0 */ export function getFilterableEither( I: Initializable, ): Filterable> { type Result = Filterable>; const init = left(I.init()); return { filter: ( (refinement: Refinement) => (ua: Either): Either => { if (isLeft(ua)) { return ua; } else if (refinement(ua.right)) { return ua as Right; } else { return init; } } ) as Result["filter"], filterMap: (fai) => (ua) => { if (isLeft(ua)) { return ua; } else { const oi = fai(ua.right); return O.isNone(oi) ? init : right(oi.value); } }, partition: ( (refinement: Refinement) => (ua: Either): Pair, Either> => { if (isLeft(ua)) { return [ua, ua]; } else if (refinement(ua.right)) { return [ua as Either, init]; } else { return [init, ua]; } } ) as Result["partition"], partitionMap: (fai) => (ua) => { if (isLeft(ua)) { return [ua, ua]; } const ei = fai(ua.right); return isLeft(ei) ? [init, right(ei.left)] : [ei, init]; }, }; } /** * @since 2.0.0 */ export const ApplicableEither: Applicable = { apply, map, wrap }; /** * @since 2.0.0 */ export const BimappableEither: Bimappable = { map, mapSecond }; /** * @since 2.0.0 */ export const FailableEither: Failable = { alt, apply, fail, flatmap, map, recover, wrap, }; /** * @since 2.0.0 */ export const FlatmappableEither: Flatmappable = { apply, flatmap, map, wrap, }; /** * @since 2.0.0 */ export const MappableEither: Mappable = { map }; /** * @since 2.0.0 */ export const FoldableEither: Foldable = { fold }; /** * @since 2.0.0 */ export const TraversableEither: Traversable = { map, fold, traverse, }; /** * @since 2.0.0 */ export const WrappableEither: Wrappable = { wrap }; /** * @since 2.0.0 */ export const tap: Tap = createTap(FailableEither); /** * @since 2.0.0 */ export const bind: Bind = createBind(FlatmappableEither); /** * @since 2.0.0 */ export const bindTo: BindTo = createBindTo(MappableEither);