/** * A Computation is a way to chain consecutive operations while collecting logs. It's * useful for guarding against unwanted input conditions, and is used in this libary for * matrix decomposition. * * @since 1.0.0 */ import * as Apl from 'fp-ts/Applicative' import * as Ap from 'fp-ts/Apply' import * as BiFun from 'fp-ts/Bifunctor' import * as Chn from 'fp-ts/Chain' import * as E from 'fp-ts/Either' import * as IO from 'fp-ts/IO' import * as FE from 'fp-ts/FromEither' import * as Fun from 'fp-ts/Functor' import * as Mon from 'fp-ts/Monad' import * as MonThrow from 'fp-ts/MonadThrow' import * as O from 'fp-ts/Option' import * as RA from 'fp-ts/ReadonlyArray' import * as RTup from 'fp-ts/ReadonlyTuple' import { flow, identity, pipe, unsafeCoerce } from 'fp-ts/function' // ############# // ### Model ### // ############# /** * @since 1.0.0 * @category Model */ export type Computation = readonly [E.Either, ReadonlyArray] // #################### // ### Constructors ### // #################### /** * @since 1.0.0 * @category Constructors */ export const of: (value: A) => Computation = value => [ E.right(value), RA.zero(), ] // ################### // ### Destructors ### // ################### /** * @since 1.1.0 * @category Destructors */ export const runComputation: (c: Computation) => E.Either = RTup.fst /** * @since 1.1.0 * @category Destructors */ export const runLogs: ( f: (e: E) => IO.IO ) => (c: Computation) => IO.IO> = f => flow(RTup.snd, IO.traverseArray(f)) /** * @since 1.1.0 * @category Destructors */ export const getOrThrow: ( onError: (e: E) => string ) => (c: Computation) => A = f => ([v]) => { if (E.isLeft(v)) { throw new Error(f(v.left)) } return v.right } /** * @since 1.1.0 * @category Destructors */ export const getOrThrowS: (c: Computation) => A = getOrThrow(identity) // ##################### // ### Non-Pipeables ### // ##################### const _map: Fun.Functor2['map'] = (fa, f) => pipe(fa, map(f)) const _bimap: BiFun.Bifunctor2['bimap'] = (fa, f, g) => pipe(fa, bimap(f, g)) const _mapLeft: BiFun.Bifunctor2['mapLeft'] = (fa, f) => pipe(fa, mapLeft(f)) const _ap: Ap.Apply2['ap'] = (fab, fa) => pipe(fa, ap(fab)) const _chain: Chn.Chain2['chain'] = (fa, f) => pipe(fa, chain(f)) // ################# // ### Instances ### // ################# /** * @since 1.0.0 * @category Instances */ export const URI = 'Computation' /** * @since 1.0.0 * @category Instances */ export type URI = typeof URI declare module 'fp-ts/HKT' { interface URItoKind2 { readonly [URI]: Computation } } /** * @since 1.0.0 * @category Instance Operations */ export const map: ( f: (a: A) => B ) => (fa: Computation) => Computation = f => RTup.mapFst(E.map(f)) /** * @since 1.0.0 * @category Instances */ export const Functor: Fun.Functor2 = { URI, map: _map, } /** * @since 1.0.0 * @category Instance Operations */ export const bimap: ( f: (e: E) => G, g: (a: A) => B ) => (fa: Computation) => Computation = (f, g) => RTup.bimap(RA.map(f), E.bimap(f, g)) /** * @since 1.0.0 * @category Instance Operations */ export const mapLeft: ( f: (e: E) => G ) => (fa: Computation) => Computation = f => RTup.bimap(RA.map(f), E.mapLeft(f)) /** * @since 1.0.0 * @category Instances */ export const Bifunctor: BiFun.Bifunctor2 = { URI, bimap: _bimap, mapLeft: _mapLeft, } /** * @since 1.0.0 * @category Instance Operations */ export const ap: ( fab: Computation B> ) => (fa: Computation) => Computation = ([fab, ls1]) => ([fa, ls2]) => [pipe(fab, E.ap(fa)), pipe(ls1, RA.concat(ls2))] /** * @since 1.0.0 * @category Instance Operations */ export const Apply: Ap.Apply2 = { ...Functor, ap: _ap, } /** * @since 1.0.0 * @category Instances */ export const Applicative: Apl.Applicative2 = { ...Apply, of, } /** * @since 1.0.0 * @category Instance Operations */ export const chainW = (f: (a: A) => Computation) => ([fa, logs]: Computation): Computation => pipe( fa, E.map(f), E.foldW( err => [E.left(err), logs], ([result, logs2]) => [result, pipe(logs, RA.concat(logs2))] ) ) /** * @since 1.0.0 * @category Instance Operations */ export const chain: ( f: (a: A) => Computation ) => (fa: Computation) => Computation = chainW /** * @since 1.0.0 * @category Instances */ export const Chain: Chn.Chain2 = { ...Apply, chain: _chain, } /** * @since 1.0.0 * @category Instances */ export const Monad: Mon.Monad2 = { ...Applicative, ...Chain, } /** * @since 1.0.0 * @category Instance Operations */ export const throwError: (e: E) => Computation = e => [E.left(e), RA.of(e)] /** * @since 1.0.0 * @category Instances */ export const MonadThrow: MonThrow.MonadThrow2 = { ...Monad, throwError, } /** * @since 1.0.0 * @category Instance Operations */ export const fromEither: FE.FromEither2['fromEither'] = a => [ a, E.isLeft(a) ? RA.of(a.left) : RA.zero(), ] /** * @since 1.0.0 * @category Instances */ export const FromEither: FE.FromEither2 = { URI, fromEither, } // ############################### // ### Natural Transformations ### // ############################### /** * @since 1.0.0 * @category Natural Transformations */ export const fromOption = FE.fromOption(FromEither) /** * @since 1.1.0 * @category Natural Transformations */ export const toOption: (c: Computation) => O.Option = flow( RTup.fst, O.fromEither ) /** * @since 1.1.0 * @category Natural Transformations */ export const fromPredicate = FE.fromPredicate(FromEither) // #################### // ### Refinements #### // #################### /** * @since 1.0.0 * @category Refinements */ export const isLeft = ( e: Computation ): e is readonly [E.Left, ReadonlyArray] => e[0]._tag === 'Left' /** * @since 1.0.0 * @category Refinements */ export const isRight = ( e: Computation ): e is readonly [E.Right, ReadonlyArray] => e[0]._tag === 'Right' // ################### // ### Combinators ### // ################### /** * @since 1.0.0 * @category Combinators */ export const apFirst = Ap.apFirst(Apply) /** * @since 1.0.0 * @category Combinators */ export const apSecond = Ap.apSecond(Apply) /** * @since 1.0.0 * @category Combinators */ export const chainFirst = Chn.chainFirst(Chain) /** * @since 1.0.0 * @category Combinators */ export const chainOptionK = FE.chainOptionK(FromEither, Chain) /** * @since 1.0.0 * @category Combinators */ export const chainEitherK = FE.chainEitherK(FromEither, Chain) // ################# // ### Utilities ### // ################# /** * @since 1.0.0 * @category Utilities */ export const tell: (message: E) => Computation = message => [ E.right(undefined), RA.of(message), ] /** * @since 1.0.0 * @category Utilities */ export const log: (message: E) => (fa: Computation) => Computation = message => ([a, logs]) => [a, pipe(logs, RA.concat(RA.of(message)))] /** * Log one message on left, and a different on right * * @since 1.1.0 * @category Utilities */ export const bilog: ( onLeft: () => E, onRight: () => E ) => (fa: Computation) => Computation = (onLeft, onRight) => c => pipe(c, log(isLeft(c) ? onLeft() : onRight())) /** * @since 1.0.0 * @category Utilities */ export const logOption: ( getOptionalMesasge: (a: A) => O.Option ) => (fa: Computation) => Computation = getOptionalMesasge => ([a, logs]) => { const optionalMessage = pipe(O.fromEither(a), O.chain(getOptionalMesasge)) return [ a, O.isSome(optionalMessage) ? pipe(logs, RA.concat(RA.of(optionalMessage.value))) : logs, ] } /** * @since 1.0.0 * @category Utilities */ export const filter: ( predicate: (a: A) => boolean, onFalse: (a: A) => E ) => (fa: Computation) => Computation = (predicate, onFalse) => chain(a => (predicate(a) ? of(a) : throwError(onFalse(a)))) /** * @since 1.0.0 * @category Utilities */ export const filterOptionK: ( test: (a: A) => O.Option, onFalse: (a: A) => E ) => (a: A) => Computation = (test, onFalse) => a => pipe( test(a), O.foldW(() => throwError(onFalse(a)), of) ) // ################### // ### Do Notation ### // ################### /** * @since 1.0.0 * @category Do Notation */ export const Do = of({}) /** * @since 1.0.0 * @category Do Notation */ export const apS = Ap.apS(Apply) /** * @since 1.0.0 * @category Do Notation */ export const bindTo = Fun.bindTo(Functor) /** * @since 1.0.0 * @category Do Notation */ export const bind = Chn.bind(Chain) /** * @since 1.0.0 * @category Do Notation */ export const bindW: ( name: Exclude, f: (a: A) => Computation ) => ( fa: Computation ) => Computation = unsafeCoerce(bind)