/** * `Env` is the core of the higher-level modules like [Ref](./Ref.ts.md) and is a `ReaderT` of [Resume](./Resume.ts.md); but * to be honest, being used so much, I didn't like writing `ReaderResume` and chose to shorten to * `Env` for the "environmental" quality Reader provides. Combining Reader and Resume allows for * creating APIs capable of utilizing dependency injection for their configuration and testability * while still combining your sync/async workflows. * * While designing application APIs it is often better to describe the logic of your system separate * from the implementation details. `Env` or rather `Reader` helps you accomplish this through the * [Dependency Inversion Principle](https://alexnault.dev/dependency-inversion-principle-in-functional-typescript). * This principle is one of the easiest ways to begin improving any codebase. * * @since 0.9.2 */ import { disposeNone } from '@most/disposable' import { Disposable } from '@most/types' import * as Alt_ from 'fp-ts/Alt' import * as FpApplicative from 'fp-ts/Applicative' import * as Ap from 'fp-ts/Apply' import * as FpChain from 'fp-ts/Chain' import { ChainRec2 } from 'fp-ts/ChainRec' import * as E from 'fp-ts/Either' import * as FIO from 'fp-ts/FromIO' import * as FR from 'fp-ts/FromReader' import * as FT from 'fp-ts/FromTask' import * as FN from 'fp-ts/function' import { bindTo as bindTo_, flap as flap_, Functor2, tupled as tupled_ } from 'fp-ts/Functor' import * as IO from 'fp-ts/IO' import { Monad2 } from 'fp-ts/Monad' import { Pointed2 } from 'fp-ts/Pointed' import * as Re from 'fp-ts/Reader' import * as RT from 'fp-ts/ReaderT' import * as RA from 'fp-ts/ReadonlyArray' import { traverse } from 'fp-ts/ReadonlyArray' import * as Task from 'fp-ts/Task' import * as FRe from './FromResume' import { FromResume2 } from './FromResume' import { ArgsOf, Arity1 } from './function' import { Intersect } from './HKT' import { MonadRec2 } from './MonadRec' import * as P from './Provide' import * as R from './Resume' import * as St from './struct' /** * Env is specialization of Reader> * @since 0.9.2 * @category Model */ export interface Env extends Re.Reader> {} /** * @since 0.9.2 * @category Type-level */ export type RequirementsOf = [A] extends [Env] ? R : [A] extends [FN.FunctionN>] ? R : never /** * @since 0.9.2 * @category Type-level */ export type ValueOf = A extends Env ? R : A extends FN.FunctionN> ? R : never /** * @since 0.9.2 * @category Combinator */ export const ap: (fa: Env) => (fab: Env>) => Env = RT.ap( R.Apply, ) /** * @since 0.9.2 * @category Combinator */ export const apW = RT.ap(R.Apply) as ( fa: Env, ) => (fab: Env>) => Env /** * @since 0.9.2 * @category Combinator */ export const chain = RT.chain(R.Chain) /** * @since 0.9.2 * @category Combinator */ export const chainW = RT.chain(R.Chain) as ( f: (a: A) => Env, ) => (ma: Env) => Env /** * @since 0.9.2 * @category Constructor */ export const fromReader: (ma: Re.Reader) => Env = RT.fromReader(R.Pointed) /** * @since 0.9.2 * @category Combinator */ export const map: (f: (a: A) => B) => (fa: Env) => Env = RT.map(R.Functor) /** * @since 0.9.2 * @category Combinator */ export const tap = (f: (a: A) => any) => (fa: Env): Env => FN.pipe( fa, map((a) => { f(a) return a }), ) /** * @since 0.9.2 * @category Combinator */ export const constant = FN.flow(FN.constant, map) /** * @since 0.9.2 * @category Model */ export type Of = Env /** * @since 0.9.2 * @category Constructor */ export const of: (a: A) => Of = RT.of(R.Pointed) /** * @since 0.9.2 * @category Constructor */ export const asksIOK: (f: (r: R) => IO.IO) => Env = RT.fromNaturalTransformation< IO.URI, R.URI >(R.fromIO) /** * @since 0.9.2 * @category Constructor */ export const asksTaskK: (f: (r: R) => Task.Task) => Env = RT.fromNaturalTransformation(R.fromTask) /** * @since 0.9.2 * @category Combinator */ export function chainRec Env>>( f: F, ): ( value: ArgsOf[0], ) => Env< RequirementsOf>, [ValueOf>] extends [E.Either] ? R : never > { return (value) => (env) => R.chainRec((a: [ValueOf>] extends [E.Either] ? R : never) => f(a)(env), )(value) } /** * @since 0.9.2 * @category URI */ export const URI = '@typed/fp/Env' /** * @since 0.9.2 * @category URI */ export type URI = typeof URI declare module 'fp-ts/HKT' { export interface URItoKind2 { [URI]: Env } } declare module './HKT' { export interface URItoVariance { [URI]: V } } /** * @since 0.9.2 * @category Instance */ export const Pointed: Pointed2 = { of, } /** * @since 0.9.2 * @category Instance */ export const Functor: Functor2 = { URI, map, } /** * @since 0.9.2 * @category Combinator */ export const flap = flap_(Functor) /** * @since 0.9.2 * @category Instance */ export const Apply: Ap.Apply2 = { ...Functor, ap, } /** * @since 0.9.2 * @category Combinator */ export const apS = Ap.apS(Apply) /** * @since 0.9.2 * @category Combinator */ export const apSW = apS as ( name: Exclude, fb: Env, ) => ( fa: Env, ) => Env /** * @since 0.9.2 * @category Combinator */ export const apT = Ap.apT(Apply) /** * @since 0.9.2 * @category Combinator */ export const apTW = apT as ( fb: Env, ) => (fas: Env) => Env /** * @since 0.9.2 * @category Combinator */ export const apFirst = Ap.apFirst(Apply) /** * @since 0.9.2 * @category Combinator */ export const apFirstW = apFirst as ( second: Env, ) => (first: Env) => Env /** * @since 0.9.2 * @category Combinator */ export const apSecond = Ap.apSecond(Apply) /** * @since 0.9.2 * @category Combinator */ export const apSecondW = apSecond as ( second: Env, ) => (first: Env) => Env /** * @since 0.9.2 * @category Typeclass Constructor */ export const getSemigroup = Ap.getApplySemigroup(Apply) /** * @since 0.9.2 * @category Instance */ export const Applicative: FpApplicative.Applicative2 = { ...Apply, ...Pointed, } /** * @since 0.9.2 * @category Typeclass Constructor */ export const getMonoid = FpApplicative.getApplicativeMonoid(Applicative) /** * @since 0.9.2 * @category Instance */ export const Chain: FpChain.Chain2 = { ...Functor, chain, } /** * @since 0.9.2 * @category Combinator */ export const chainFirst = FpChain.chainFirst(Chain) /** * @since 0.9.2 * @category Combinator */ export const chainFirstW = chainFirst as ( f: (a: A) => Env, ) => (first: Env) => Env /** * @since 0.9.2 * @category Combinator */ export const flattenW = chain(FN.identity) as ( env: Env>, ) => Env /** * @since 0.9.2 * @category Combinator */ export const flatten = chain(FN.identity) as (env: Env>) => Env /** * @since 0.9.2 * @category Instance */ export const Monad: Monad2 = { ...Chain, ...Pointed, } /** * @since 0.9.2 * @category Instance */ export const ChainRec: ChainRec2 = { URI, chainRec, } /** * @since 0.9.2 * @category Instance */ export const MonadRec: MonadRec2 = { ...Monad, chainRec, } /** * @since 0.9.2 * @category Instance */ export const FromReader: FR.FromReader2 = { fromReader: (reader) => (e) => R.sync(() => reader(e)), } /** * @since 0.9.2 * @category Combinator */ export const raceW = (a: Env) => (b: Env): Env => (e) => R.race(a(e))(b(e)) /** * @since 0.9.2 * @category Combinator */ export const race = (a: Env) => (b: Env): Env => (e) => R.race(a(e))(b(e)) /** * @since 0.9.2 * @category Instance */ export const Alt: Alt_.Alt2 = { ...Functor, alt: (snd: FN.Lazy>) => (fst: Env) => raceW(fst)(snd()), } /** * @since 0.9.2 * @category Combinator */ export const alt = Alt.alt /** * @since 0.9.2 * @category Combinator */ export const altW = alt as ( snd: FN.Lazy>, ) => (fst: Env) => Env /** * @since 0.9.2 * @category Combinator */ export const altAll = Alt_.altAll(Alt) /** * @since 0.9.2 * @category Constructor */ export const fromIO = fromReader as (fa: IO.IO) => Env /** * @since 0.9.2 * @category Instance */ export const FromIO: FIO.FromIO2 = { URI, fromIO, } /** * @since 0.9.2 * @category Instance */ export const FromTask: FT.FromTask2 = { ...FromIO, fromTask: (task) => () => R.fromTask(task), } /** * @since 0.9.2 * @category Constructor */ export const fromTask = FromTask.fromTask as (fa: Task.Task) => Env /** * @since 0.9.2 * @category Constructor */ export const fromResume: (resume: R.Resume) => Env = FN.constant /** * @since 0.9.2 * @category Combinator */ export const FromResume: FromResume2 = { fromResume, } /** * @since 0.9.2 * @category Combinator */ export const useSome = (provided: E1) => (env: Env): Env => (e) => env({ ...e, ...provided }) /** * @since 0.9.2 * @category Combinator */ export const provideSome = (provided: E1) => (env: Env): Env => (e) => env({ ...provided, ...e }) /** * @since 0.9.2 * @category Combinator */ export const useAll = (provided: E1) => (env: Env): Env => () => env(provided) /** * @since 0.9.2 * @category Combinator */ export const provideAll = (provided: E1) => (env: Env): Env => (e) => env({ ...provided, ...((e as any) ?? {}) }) /** * @since 0.9.2 * @category Instance */ export const UseSome: P.UseSome2 = { useSome, } /** * @since 0.9.2 * @category Instance */ export const UseAll: P.UseAll2 = { useAll, } /** * @since 0.9.2 * @category Instance */ export const ProvideSome: P.ProvideSome2 = { provideSome, } /** * @since 0.9.2 * @category Instance */ export const ProvideAll: P.ProvideAll2 = { provideAll, } /** * @since 0.9.2 * @category Combinator */ export const provideAllWith = P.provideAllWith({ ...ProvideAll, ...Chain }) /** * @since 0.9.2 * @category Combinator */ export const useAllWith = P.useAllWith({ ...UseAll, ...Chain }) /** * @since 0.9.2 * @category Combinator */ export const provideSomeWith = P.provideSomeWith({ ...ProvideSome, ...Chain }) /** * @since 0.9.2 * @category Combinator */ export const useSomeWith = P.useSomeWith({ ...UseSome, ...Chain }) /** * @since 0.9.2 * @category Combinator */ export const askAndUse = P.askAndUse({ ...UseAll, ...Chain, ...FromReader }) /** * @since 0.9.2 * @category Combinator */ export const askAndProvide = P.askAndProvide({ ...ProvideAll, ...Chain, ...FromReader }) /** * @since 0.9.2 * @category Combinator */ export const toResume = FN.flow( askAndUse, map((e) => e({})), ) /** * @since 0.9.2 * @category Instance */ export const Provide: P.Provide2 = { useSome, useAll, provideSome, provideAll, } /** * @since 0.9.2 * @category Constructor */ export const Do: Env = fromIO(() => Object.create(null)) /** * @since 0.9.2 * @category Combinator */ export const bindTo = bindTo_(Functor) /** * @since 0.9.2 * @category Combinator */ export const bind = FpChain.bind(Monad) /** * @since 0.9.2 * @category Combinator */ export const bindW = FpChain.bind(Monad) as ( name: Exclude, f: (a: A) => Env, ) => ( ma: Env, ) => Env /** * @since 0.9.2 * @category Combinator */ export const tupled = tupled_(Functor) /** * @since 0.9.2 * @category Constructor */ export const ask = FR.ask(FromReader) /** * @since 0.9.2 * @category Constructor */ export const asks = FR.asks(FromReader) /** * @since 0.9.2 * @category Constructor */ export const asksE: (f: (r: R) => Env) => Env = FN.flow(asks, flattenW) /** * @since 0.9.2 * @category Combinator */ export const chainReaderK = FR.chainReaderK(FromReader, Chain) /** * @since 0.9.2 * @category Constructor */ export const fromReaderK = FR.fromReaderK(FromReader) /** * @since 0.9.2 * @category Combinator */ export const chainFirstResumeK = FRe.chainFirstResumeK(FromResume, Chain) /** * @since 0.9.2 * @category Combinator */ export const chainResumeK = FRe.chainResumeK(FromResume, Chain) /** * @since 0.9.2 * @category Constructor */ export const fromResumeK = FRe.fromResumeK(FromResume) /** * @since 0.9.2 * @category Combinator */ export const chainFirstTaskK = FT.chainFirstTaskK(FromTask, Chain) /** * @since 0.9.2 * @category Combinator */ export const chainTaskK = FT.chainTaskK(FromTask, Chain) /** * @since 0.9.2 * @category Constructor */ export const fromTaskK = FT.fromTaskK(FromTask) /** * @since 0.9.2 * @category Combinator */ export const chainFirstIOK = FIO.chainFirstIOK(FromIO, Chain) /** * @since 0.9.2 * @category Combinator */ export const chainIOK = FIO.chainIOK(FromIO, Chain) /** * @since 0.9.2 * @category Constructor */ export const fromIOK = FIO.fromIOK(FromIO) /** * @since 0.9.2 * @category Combinator */ export const zip = traverse(Applicative)((x: Env) => x) /** * @since 0.9.2 * @category Combinator */ export const zipW = zip as unknown as >>( envs: A, ) => Env }>, { [K in keyof A]: ValueOf }> /** * @since 0.9.2 * @category Combinator */ export const combineAll = >>(...envs: A) => zipW(envs) /** * @since 0.11.0 * @category Combinator */ export const combineStruct = >>>( props: Props, ) => FN.pipe( combineAll( ...FN.pipe( Object.entries(props), RA.map(([k, env]) => FN.pipe( env, map((v) => St.make(k, v)), ), ), ), ), map((o) => Object.assign({}, ...o) as { readonly [K in keyof Props]: ValueOf }), ) /** * @since 0.9.2 * @category Execution */ export const runWith = (f: (value: A) => Disposable) => (requirements: E) => (env: Env): Disposable => FN.pipe(requirements, env, R.run(f)) /** * @since 0.9.2 * @category Execution */ export const execWith = runWith(disposeNone) /** * Construct an Env to a lazily-defined Env-based effect that must be provided later. * Does not support functions which require type-parameters as they will resolve to unknown, due * to limitations in TS, if you need this maybe use [asksE](#askse) * @since 0.9.2 * @category Constructor */ export const op = Env>() => ( key: K, ): { (...args: ArgsOf): Env< RequirementsOf> & { readonly [_ in K]: F }, ValueOf> > readonly key: K } => { function operation( ...args: ArgsOf ): Env> & { readonly [_ in K]: F }, ValueOf>> { return FN.pipe( ask<{ readonly [_ in K]: F }>(), chain((e) => e[key](...args)), ) } operation.key = key return operation } /** * @since 0.9.2 * @category Combinator */ export const toResumeK = (envK: (...args: Args) => Env) => FN.pipe( ask(), map( (e) => (...args: Args) => FN.pipe(e, envK(...args)), ), )