import { Alt3, Alt3C } from 'fp-ts/lib/Alt' import { Applicative3, Applicative3C } from 'fp-ts/lib/Applicative' import { Apply1 } from 'fp-ts/lib/Apply' import { Bifunctor3 } from 'fp-ts/lib/Bifunctor' import * as E from 'fp-ts/lib/Either' import { flow, identity, Lazy, pipe, Predicate, Refinement, unsafeCoerce, } from 'fp-ts/lib/function' import { Functor3 } from 'fp-ts/lib/Functor' import * as I from 'fp-ts/lib/IO' import * as IE from 'fp-ts/lib/IOEither' import { Monad3, Monad3C } from 'fp-ts/lib/Monad' import { MonadIO3, MonadIO3C } from 'fp-ts/lib/MonadIO' import { MonadThrow3, MonadThrow3C } from 'fp-ts/lib/MonadThrow' import { Monoid } from 'fp-ts/lib/Monoid' import { Option } from 'fp-ts/lib/Option' import * as R from 'fp-ts/lib/Reader' import * as RE from 'fp-ts/lib/ReaderEither' import * as S from 'fp-ts/lib/Semigroup' import * as RI from 'fp-ts-contrib/lib/ReaderIO' import { EmptyEnv, Subtract, } from '@monorail/sharedHelpers/fp-ts-ext/effectsUtils' import { noOpIO } from '@monorail/sharedHelpers/fp-ts-ext/IO' // ------------------------------------------------------------------------------------- // model // ------------------------------------------------------------------------------------- /* eslint-disable @typescript-eslint/prefer-function-type */ export interface ReaderIOEither { (r: R): IE.IOEither } /* eslint-enable @typescript-eslint/prefer-function-type */ // ------------------------------------------------------------------------------------- // internal utilities // ------------------------------------------------------------------------------------- /* eslint-disable @typescript-eslint/no-explicit-any */ type SafeAny = any /* eslint-enable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable prefer-object-spread */ export const bind_ = ( a: A, name: Exclude, b: B, ): { [K in keyof A | N]: K extends keyof A ? A[K] : B } => Object.assign({}, a, { [name]: b }) as SafeAny /** * @internal */ export const bindTo_ = (name: N) => ( b: B, ): { [K in N]: B } => ({ [name]: b } as SafeAny) /* eslint-enable prefer-object-spread */ /* eslint-enable @typescript-eslint/no-unsafe-return */ // ------------------------------------------------------------------------------------- // constructors // ------------------------------------------------------------------------------------- /** * @category constructors */ export const fromIOEither: ( ma: IE.IOEither, ) => ReaderIOEither = R.of /** * @category constructors */ export const left: ( e: E, ) => ReaderIOEither = flow(IE.left, fromIOEither) /** * @category constructors */ export const right: ( a: A, ) => ReaderIOEither = flow(IE.right, fromIOEither) /** * @category constructors */ export const rightIO: ( ma: I.IO, ) => ReaderIOEither = flow(IE.rightIO, fromIOEither) /** * @category constructors */ export const leftIO: ( me: I.IO, ) => ReaderIOEither = flow(IE.leftIO, fromIOEither) /** * @category constructors */ export const rightReader: ( ma: R.Reader, ) => ReaderIOEither = ma => flow(ma, IE.right) /** * @category constructors */ export const leftReader: ( me: R.Reader, ) => ReaderIOEither = me => flow(me, IE.left) /** * @category constructors */ export const rightReaderIO: ( ma: RI.ReaderIO, ) => ReaderIOEither = ma => flow(ma, IE.rightIO) /** * @category constructors */ export const leftReaderIO: ( me: RI.ReaderIO, ) => ReaderIOEither = me => flow(me, IE.leftIO) /** * @category constructors */ export const fromReaderEither = ( ma: RE.ReaderEither, ): ReaderIOEither => flow(ma, IE.fromEither) /** * @category constructors */ export const ask: () => ReaderIOEither = () => IE.right /** * @category constructors */ export const asks: ( f: (r: R) => A, ) => ReaderIOEither = f => flow(IE.right, IE.map(f)) /** * Derivable from `MonadThrow`. * * @category constructors */ export const fromEither: ( ma: E.Either, ) => ReaderIOEither = E.fold(left, a => right(a)) /** * Derivable from `MonadThrow`. * * @category constructors */ export const fromOption: ( onNone: Lazy, ) => (ma: Option) => ReaderIOEither = onNone => ma => ma._tag === 'None' ? left(onNone()) : right(ma.value) /** * Derivable from `MonadThrow`. * * @category constructors */ export const fromPredicate: { (refinement: Refinement, onFalse: (a: A) => E): ( a: A, ) => ReaderIOEither (predicate: Predicate, onFalse: (a: A) => E): ( a: A, ) => ReaderIOEither } = (predicate: Predicate, onFalse: (a: A) => E) => (a: A) => predicate(a) ? right(a) : left(onFalse(a)) // ------------------------------------------------------------------------------------- // destructors // ------------------------------------------------------------------------------------- /** * @category destructors */ export function fold( onLeft: (e: E) => RI.ReaderIO, onRight: (a: A) => RI.ReaderIO, ): (ma: ReaderIOEither) => RI.ReaderIO { return ma => r => pipe( ma(r), IE.fold( e => onLeft(e)(r), a => onRight(a)(r), ), ) } /** * Less strict version of [`getOrElse`](#getOrElse). * * @category destructors */ export const getOrElseW = ( onLeft: (e: E) => RI.ReaderIO, ): (( ma: ReaderIOEither, ) => RI.ReaderIO) => ma => r => IE.getOrElseW((e: E) => onLeft(e)(r))(ma(r)) /** * @category destructors */ export const getOrElse: ( onLeft: (e: E) => RI.ReaderIO, ) => (ma: ReaderIOEither) => RI.ReaderIO = getOrElseW // ------------------------------------------------------------------------------------- // combinators // ------------------------------------------------------------------------------------- /** * @category combinators */ export function orElse( onLeft: (e: E) => ReaderIOEither, ): (ma: ReaderIOEither) => ReaderIOEither { return ma => r => IE.orElse(e => onLeft(e)(r))(ma(r)) } /** * @category combinators */ export const swap = ( ma: ReaderIOEither, ): ReaderIOEither => flow(ma, IE.swap) /** * @category combinators * * NOTE: The equivalent of this for ReaderTaskEither is being removed in fp-ts version 3.0. */ export const local: ( f: (f: Q) => R, ) => (ma: ReaderIOEither) => ReaderIOEither = R.local /** * Less strict version of [`filterOrElse`](#filterOrElse). * * @category combinators */ export const filterOrElseW: { (refinement: Refinement, onFalse: (a: A) => E2): < R, E1 >( ma: ReaderIOEither, ) => ReaderIOEither (predicate: Predicate, onFalse: (a: A) => E2): ( ma: ReaderIOEither, ) => ReaderIOEither } = ( predicate: Predicate, onFalse: (a: A) => E2, ): ((ma: ReaderIOEither) => ReaderIOEither) => chainW(a => (predicate(a) ? right(a) : left(onFalse(a)))) /** * Derivable from `MonadThrow`. * * @category combinators */ export const filterOrElse: { (refinement: Refinement, onFalse: (a: A) => E): ( ma: ReaderIOEither, ) => ReaderIOEither (predicate: Predicate, onFalse: (a: A) => E): ( ma: ReaderIOEither, ) => ReaderIOEither } = filterOrElseW /** * @category combinators */ export function fromEitherK, B>( f: (...a: A) => E.Either, ): (...a: A) => ReaderIOEither { return (...a) => fromEither(f(...a)) } /** * Less strict version of [`chainEitherK`](#chainEitherK). * * @category combinators */ export const chainEitherKW: ( f: (a: A) => E.Either, ) => (ma: ReaderIOEither) => ReaderIOEither = f => chainW(fromEitherK(f)) /** * @category combinators */ export const chainEitherK: ( f: (a: A) => E.Either, ) => (ma: ReaderIOEither) => ReaderIOEither = chainEitherKW /** * @category combinators */ export function fromIOEitherK, B>( f: (...a: A) => IE.IOEither, ): (...a: A) => ReaderIOEither { return (...a) => fromIOEither(f(...a)) } /** * Less strict version of [`chainIOEitherK`](#chainIOEitherK). * * @category combinators */ export const chainIOEitherKW: ( f: (a: A) => IE.IOEither, ) => (ma: ReaderIOEither) => ReaderIOEither = f => chainW(fromIOEitherK(f)) /** * @category combinators */ export const chainIOEitherK: ( f: (a: A) => IE.IOEither, ) => ( ma: ReaderIOEither, ) => ReaderIOEither = chainIOEitherKW // ------------------------------------------------------------------------------------- // non-pipeables // ------------------------------------------------------------------------------------- const map_: Monad3['map'] = (fa, f) => pipe(fa, map(f)) const apPar_: Monad3['ap'] = (fab, fa) => pipe(fab, ap(fa)) const apSeq_: Monad3['ap'] = (fab, fa) => pipe( fab, chain(f => pipe(fa, map(f))), ) const chain_: Monad3['chain'] = (ma, f) => pipe(ma, chain(f)) const alt_: Alt3['alt'] = (fa, that) => pipe(fa, alt(that)) const bimap_: Bifunctor3['bimap'] = (fa, f, g) => pipe(fa, bimap(f, g)) const mapLeft_: Bifunctor3['mapLeft'] = (fa, f) => pipe(fa, mapLeft(f)) // ------------------------------------------------------------------------------------- // pipeables // ------------------------------------------------------------------------------------- export const map: ( f: (a: A) => B, ) => (fa: ReaderIOEither) => ReaderIOEither = f => fa => flow(fa, IE.map(f)) /** * Map a pair of functions over the two last type arguments of the bifunctor. * * @category Bifunctor */ export const bimap: ( f: (e: E) => G, g: (a: A) => B, ) => (fa: ReaderIOEither) => ReaderIOEither = ( f, g, ) => fa => r => pipe(fa(r), IE.bimap(f, g)) /** * Map a function over the second type argument of a bifunctor. * * @category Bifunctor */ export const mapLeft: ( f: (e: E) => G, ) => ( fa: ReaderIOEither, ) => ReaderIOEither = f => fa => r => pipe(fa(r), IE.mapLeft(f)) /** * Less strict version of [`ap`](#ap). * * @category Apply */ export const apW = (fa: ReaderIOEither) => ( fab: ReaderIOEither B>, ): ReaderIOEither => r => pipe(fab(r), IE.apW(fa(r))) /** * Apply a function to an argument under a type constructor. * * @category Apply */ export const ap: ( fa: ReaderIOEither, ) => ( fab: ReaderIOEither B>, ) => ReaderIOEither = apW /** * Combine two effectful actions, keeping only the result of the first. * * Derivable from `Apply`. * * @category combinators */ export const apFirst: ( fb: ReaderIOEither, ) => (fa: ReaderIOEither) => ReaderIOEither = fb => flow( map(a => () => a), ap(fb), ) /** * Combine two effectful actions, keeping only the result of the second. * * Derivable from `Apply`. * * @category combinators */ export const apSecond = ( fb: ReaderIOEither, ): ((fa: ReaderIOEither) => ReaderIOEither) => flow( map(() => (b: B) => b), ap(fb), ) /** * Wrap a value into the type constructor. * * Equivalent to [`right`](#right). * * @category Applicative */ export const of: Applicative3['of'] = right /** * Less strict version of [`chain`](#chain). * * @category Monad */ export const chainW: ( f: (a: A) => ReaderIOEither, ) => ( ma: ReaderIOEither, ) => ReaderIOEither = f => fa => r => pipe( fa(r), IE.chainW(a => f(a)(r)), ) /** * Composes computations in sequence, using the return value of one computation to determine the next computation. * * @category Monad */ export const chain: ( f: (a: A) => ReaderIOEither, ) => (ma: ReaderIOEither) => ReaderIOEither = chainW /** * Less strict version of [`chainFirst`](#chainFirst). * * Derivable from `Monad`. * * @category combinators */ export const chainFirstW: ( f: (a: A) => ReaderIOEither, ) => ( ma: ReaderIOEither, ) => ReaderIOEither = f => chainW(a => pipe( f(a), map(() => a), ), ) /** * Composes computations in sequence, using the return value of one computation to determine the next computation and * keeping only the result of the first. * * Derivable from `Monad`. * * @category combinators */ export const chainFirst: ( f: (a: A) => ReaderIOEither, ) => (ma: ReaderIOEither) => ReaderIOEither = chainFirstW /** * Derivable from `Monad`. * * @category combinators */ export const flatten: ( mma: ReaderIOEither>, ) => ReaderIOEither = chain(identity) /** * Less strict version of [`alt`](#alt). * * @category Alt */ export const altW = (that: () => ReaderIOEither) => < R1, E1, A >( fa: ReaderIOEither, ): ReaderIOEither => r => pipe( fa(r), IE.altW(() => that()(r)), ) /** * Identifies an associative operation on a type constructor. It is similar to `Semigroup`, except that it applies to * types of kind `* -> *`. * * @category Alt */ export const alt: ( that: () => ReaderIOEither, ) => (fa: ReaderIOEither) => ReaderIOEither = altW /** * @category MonadIO */ export const fromIO: MonadIO3['fromIO'] = rightIO /** * @category MonadThrow */ export const throwError: MonadThrow3['throwError'] = left // ------------------------------------------------------------------------------------- // instances // ------------------------------------------------------------------------------------- export const URI = 'ReaderIOEither' export type URI = typeof URI declare module 'fp-ts/lib/HKT' { interface URItoKind3 { readonly [URI]: ReaderIOEither } } /** * Semigroup returning the left-most non-`Left` value. If both operands are `Right`s then the inner values are * concatenated using the provided `Semigroup` * * @category instances */ export function getSemigroup( S: S.Semigroup, ): S.Semigroup> { return R.getSemigroup(IE.getSemigroup(S)) } /** * Semigroup returning the left-most `Left` value. If both operands are `Right`s then the inner values * are concatenated using the provided `Semigroup` * * @category instances */ export function getApplySemigroup( S: S.Semigroup, ): S.Semigroup> { return R.getSemigroup(IE.getApplySemigroup(S)) } /** * @category instances */ export function getApplyMonoid( M: Monoid, ): Monoid> { return { concat: getApplySemigroup(M).concat, empty: right(M.empty), } } /** * @category instances */ export function getApplicativeReaderIOValidation( A: Apply1, SE: S.Semigroup, ): Applicative3C { const AV = IE.getApplicativeIOValidation(SE) const ap = ( fga: R.Reader>, ): (( fgab: R.Reader B>>, ) => R.Reader>) => flow( R.map(gab => (ga: IE.IOEither) => AV.ap(gab, ga)), R.ap(fga), ) return { URI, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment _E: undefined as SafeAny, map: map_, ap: (fab, fa) => pipe(fab, ap(fa)), of, } } /** * @category instances */ export function getAltReaderIOValidation(SE: S.Semigroup): Alt3C { const A = IE.getAltIOValidation(SE) return { URI, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment _E: undefined as SafeAny, map: map_, alt: (me, that) => r => A.alt(me(r), () => that()(r)), } } /** * @category instances * * NOTE: This is just for convenience. This style of exporting the full set of type class instances is being removed in fp-ts version 3. */ export function getReaderIOValidation( SE: S.Semigroup, ): Monad3C & Bifunctor3 & Alt3C & MonadIO3C & MonadThrow3C { const applicativeReaderIOValidation = getApplicativeReaderIOValidation( I.Applicative, SE, ) const altReaderIOValidation = getAltReaderIOValidation(SE) return { URI, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment _E: undefined as SafeAny, map: map_, of, chain: chain_, bimap: bimap_, mapLeft: mapLeft_, ap: applicativeReaderIOValidation.ap, alt: altReaderIOValidation.alt, fromIO, throwError, } } /** * @category instances */ export const Functor: Functor3 = { URI, map: map_, } /** * @category instances * @since 2.8.4 */ export const ApplicativePar: Applicative3 = { URI, map: map_, ap: apPar_, of, } /** * @category instances * @since 2.8.4 */ export const ApplicativeSeq: Applicative3 = { URI, map: map_, ap: apSeq_, of, } /** * @category instances */ export const Bifunctor: Bifunctor3 = { URI, bimap: bimap_, mapLeft: mapLeft_, } /** * @category instances */ export const Alt: Alt3 = { URI, map: map_, alt: alt_, } /** * @category instances * * NOTE: This is just for convenience. This style of exporting the full set of * type class instances is being removed in fp-ts version 3. */ export const readerIOEither: Monad3 & Bifunctor3 & Alt3 & MonadIO3 & MonadThrow3 = { URI, map: map_, of, ap: apPar_, chain: chain_, alt: alt_, bimap: bimap_, mapLeft: mapLeft_, fromIO, throwError, } /** * @category instances * * NOTE: This is just for convenience. This style of exporting the full set of * type class instances is being removed in fp-ts version 3. */ export const readerIOEitherSeq: typeof readerIOEither = { URI, map: map_, of, ap: apSeq_, chain: chain_, alt: alt_, bimap: bimap_, mapLeft: mapLeft_, fromIO, throwError, } // ------------------------------------------------------------------------------------- // utils // ------------------------------------------------------------------------------------- /** * NOTE: The equivalent of this for ReaderTaskEither is being removed in fp-ts version 3.0. */ export function run( ma: ReaderIOEither, r: R, ): E.Either { return ma(r)() } /** * Make sure that a resource is cleaned up in the event of an exception (\*). The release action is called regardless of * whether the body action throws (\*) or returns. * * (\*) i.e. returns a `Left` * * Derivable from `MonadThrow`. */ export function bracket( aquire: ReaderIOEither, use: (a: A) => ReaderIOEither, release: (a: A, e: E.Either) => ReaderIOEither, ): ReaderIOEither { return r => IE.bracket( aquire(r), a => use(a)(r), (a, e) => release(a, e)(r), ) } // ------------------------------------------------------------------------------------- // do notation // ------------------------------------------------------------------------------------- export const Do: ReaderIOEither = of({}) export const bindTo = ( name: N, ): (( fa: ReaderIOEither, ) => ReaderIOEither) => map(bindTo_(name)) export const bindW = ( name: Exclude, f: (a: A) => ReaderIOEither, ): (( fa: ReaderIOEither, ) => ReaderIOEither< Q & R, E | D, { [K in keyof A | N]: K extends keyof A ? A[K] : B } >) => chainW(a => pipe( f(a), map(b => bind_(a, name, b)), ), ) export const bind: ( name: Exclude, f: (a: A) => ReaderIOEither, ) => ( fa: ReaderIOEither, ) => ReaderIOEither< R, E, { [K in keyof A | N]: K extends keyof A ? A[K] : B } > = bindW // ------------------------------------------------------------------------------------- // pipeable sequence S // ------------------------------------------------------------------------------------- export const apSW = ( name: Exclude, fb: ReaderIOEither, ): (( fa: ReaderIOEither, ) => ReaderIOEither< Q & R, D | E, { [K in keyof A | N]: K extends keyof A ? A[K] : B } >) => flow( map(a => (b: B) => bind_(a, name, b)), apW(fb), ) export const apS: ( name: Exclude, fb: ReaderIOEither, ) => ( fa: ReaderIOEither, ) => ReaderIOEither< R, E, { [K in keyof A | N]: K extends keyof A ? A[K] : B } > = apSW // ------------------------------------------------------------------------------------- // array utils // ------------------------------------------------------------------------------------- // missing from ReaderIO implementation in fp-ts-contrib const readerIO_traverseArrayWithIndex: ( f: (index: number, a: A) => RI.ReaderIO, ) => (arr: ReadonlyArray) => RI.ReaderIO> = f => flow(R.traverseArrayWithIndex(f), R.map(I.sequenceArray)) export const traverseArrayWithIndex: ( f: (index: number, a: A) => ReaderIOEither, ) => (arr: ReadonlyArray) => ReaderIOEither> = f => flow(readerIO_traverseArrayWithIndex(f), RI.map(E.sequenceArray)) export const traverseArray: ( f: (a: A) => ReaderIOEither, ) => (arr: ReadonlyArray) => ReaderIOEither> = f => traverseArrayWithIndex((_, a) => f(a)) export const sequenceArray: ( arr: ReadonlyArray>, ) => ReaderIOEither> = traverseArray(identity) export const traverseSeqArrayWithIndex: ( f: (index: number, a: A) => ReaderIOEither, ) => ( arr: ReadonlyArray, ) => ReaderIOEither> = f => arr => r => () => { const result = [] for (let i = 0; i < arr.length; i++) { const b = f(i, arr[i])(r)() if (E.isLeft(b)) { return b } result.push(b.right) } return E.right(result) } export const traverseSeqArray: ( f: (a: A) => ReaderIOEither, ) => (arr: ReadonlyArray) => ReaderIOEither> = f => traverseSeqArrayWithIndex((_, a) => f(a)) export const sequenceSeqArray: ( arr: ReadonlyArray>, ) => ReaderIOEither> = traverseSeqArray(identity) // ------------------------------------------------------------------------------------- // Effect extension functions (similar to the ReaderTaskEither file in this directory) // ------------------------------------------------------------------------------------- export const orElseW = ( f: (e: E) => ReaderIOEither, ) => (rie: ReaderIOEither): ReaderIOEither => pipe(rie, orElse(unsafeCoerce(f))) export const chainWFirst = ( f: (a: A) => ReaderIOEither, ) => ( ma: ReaderIOEither, ): ReaderIOEither => env => () => { const ea = run(ma, env) return pipe( ea, E.fold | E.Either>( err => E.left(err), a => { run(f(a), env) return ea }, ), ) } export const withEnv = () => ( r: ReaderIOEither, ): ReaderIOEither => pipe( ask(), chainW(() => r), ) export const toLeft = ( riea: ReaderIOEither, ): ReaderIOEither => env => () => pipe( run(riea, env), E.fold( e => E.left(e), a => E.left(a), ), ) export const onLeft = ( f: (e: E) => ReaderIOEither, ) => (rie: ReaderIOEither): ReaderIOEither => pipe( rie, orElseW(e => { return pipe( toLeft(f(e)), mapLeft(() => e), ) }), ) export const runP = (r: R) => ( rie: ReaderIOEither, ): E.Either => run(rie, r) /** * A ReaderIOEither that performs a noOp computation * * It uses an empty environment and cannot fail. */ export const noOpRIE = fromIO(noOpIO) /** * Provides the required environment to a ReaderIOEither, * converting it into a TaskEither. * * Similar to `runP` but delays execution. */ export const provide = (r: R) => ( rie: ReaderIOEither, ): IE.IOEither => () => run(rie, r) /** * Provides a subset of a ReaderIOEither's required environment, * returning a new ReaderIOEither with a narrowed environment requirement. * * Similar to `provide` but does not completely fulfill the RIE's requirements. * Think of this as partial application for RIE dependencies. */ export const providePartial = (q: Q) => ( rie: ReaderIOEither, ): ReaderIOEither, E, A> => (r: Subtract) => () => run(rie, { ...q, ...r } as Q & R)