@typed/fp
Version:
Data Structures and Resources for fp-ts
835 lines (733 loc) • 16.6 kB
text/typescript
/**
* `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<E, A>` and chose to shorten to
* `Env<E, A>` 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<R, Resume<A>>
* @since 0.9.2
* @category Model
*/
export interface Env<R, A> extends Re.Reader<R, R.Resume<A>> {}
/**
* @since 0.9.2
* @category Type-level
*/
export type RequirementsOf<A> = [A] extends [Env<infer R, any>]
? R
: [A] extends [FN.FunctionN<any, Env<infer R, any>>]
? R
: never
/**
* @since 0.9.2
* @category Type-level
*/
export type ValueOf<A> = A extends Env<any, infer R>
? R
: A extends FN.FunctionN<any, Env<any, infer R>>
? R
: never
/**
* @since 0.9.2
* @category Combinator
*/
export const ap: <R, A>(fa: Env<R, A>) => <B>(fab: Env<R, Arity1<A, B>>) => Env<R, B> = RT.ap(
R.Apply,
)
/**
* @since 0.9.2
* @category Combinator
*/
export const apW = RT.ap(R.Apply) as <R1, A>(
fa: Env<R1, A>,
) => <R2, B>(fab: Env<R2, Arity1<A, B>>) => Env<R1 & R2, B>
/**
* @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 <A, R1, B>(
f: (a: A) => Env<R1, B>,
) => <R2>(ma: Env<R2, A>) => Env<R1 & R2, B>
/**
* @since 0.9.2
* @category Constructor
*/
export const fromReader: <R, A>(ma: Re.Reader<R, A>) => Env<R, A> = RT.fromReader(R.Pointed)
/**
* @since 0.9.2
* @category Combinator
*/
export const map: <A, B>(f: (a: A) => B) => <R>(fa: Env<R, A>) => Env<R, B> = RT.map(R.Functor)
/**
* @since 0.9.2
* @category Combinator
*/
export const tap =
<A>(f: (a: A) => any) =>
<R>(fa: Env<R, A>): Env<R, A> =>
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<A> = Env<unknown, A>
/**
* @since 0.9.2
* @category Constructor
*/
export const of: <A>(a: A) => Of<A> = RT.of(R.Pointed)
/**
* @since 0.9.2
* @category Constructor
*/
export const asksIOK: <R, A>(f: (r: R) => IO.IO<A>) => Env<R, A> = RT.fromNaturalTransformation<
IO.URI,
R.URI
>(R.fromIO)
/**
* @since 0.9.2
* @category Constructor
*/
export const asksTaskK: <R, A>(f: (r: R) => Task.Task<A>) => Env<R, A> =
RT.fromNaturalTransformation<Task.URI, R.URI>(R.fromTask)
/**
* @since 0.9.2
* @category Combinator
*/
export function chainRec<F extends (value: any) => Env<any, E.Either<any, any>>>(
f: F,
): (
value: ArgsOf<F>[0],
) => Env<
RequirementsOf<ReturnType<F>>,
[ValueOf<ReturnType<F>>] extends [E.Either<any, infer R>] ? R : never
> {
return (value) => (env) =>
R.chainRec((a: [ValueOf<ReturnType<F>>] extends [E.Either<any, infer R>] ? 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<E, A> {
[URI]: Env<E, A>
}
}
declare module './HKT' {
export interface URItoVariance {
[URI]: V<E, Contravariant>
}
}
/**
* @since 0.9.2
* @category Instance
*/
export const Pointed: Pointed2<URI> = {
of,
}
/**
* @since 0.9.2
* @category Instance
*/
export const Functor: Functor2<URI> = {
URI,
map,
}
/**
* @since 0.9.2
* @category Combinator
*/
export const flap = flap_(Functor)
/**
* @since 0.9.2
* @category Instance
*/
export const Apply: Ap.Apply2<URI> = {
...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 <N extends string, A, E1, B>(
name: Exclude<N, keyof A>,
fb: Env<E1, B>,
) => <E2>(
fa: Env<E2, A>,
) => Env<E1 & E2, { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }>
/**
* @since 0.9.2
* @category Combinator
*/
export const apT = Ap.apT(Apply)
/**
* @since 0.9.2
* @category Combinator
*/
export const apTW = apT as <E1, B>(
fb: Env<E1, B>,
) => <E2, A extends readonly unknown[]>(fas: Env<E2, A>) => Env<E1 & E2, readonly [...A, B]>
/**
* @since 0.9.2
* @category Combinator
*/
export const apFirst = Ap.apFirst(Apply)
/**
* @since 0.9.2
* @category Combinator
*/
export const apFirstW = apFirst as <E1, B>(
second: Env<E1, B>,
) => <E2, A>(first: Env<E2, A>) => Env<E1 & E2, A>
/**
* @since 0.9.2
* @category Combinator
*/
export const apSecond = Ap.apSecond(Apply)
/**
* @since 0.9.2
* @category Combinator
*/
export const apSecondW = apSecond as <E1, B>(
second: Env<E1, B>,
) => <E2, A>(first: Env<E2, A>) => Env<E1 & E2, B>
/**
* @since 0.9.2
* @category Typeclass Constructor
*/
export const getSemigroup = Ap.getApplySemigroup(Apply)
/**
* @since 0.9.2
* @category Instance
*/
export const Applicative: FpApplicative.Applicative2<URI> = {
...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<URI> = {
...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 <A, E1, B>(
f: (a: A) => Env<E1, B>,
) => <E2>(first: Env<E2, A>) => Env<E1 & E2, A>
/**
* @since 0.9.2
* @category Combinator
*/
export const flattenW = chain(FN.identity) as <E1, E2, A>(
env: Env<E1, Env<E2, A>>,
) => Env<E1 & E2, A>
/**
* @since 0.9.2
* @category Combinator
*/
export const flatten = chain(FN.identity) as <E, A>(env: Env<E, Env<E, A>>) => Env<E, A>
/**
* @since 0.9.2
* @category Instance
*/
export const Monad: Monad2<URI> = {
...Chain,
...Pointed,
}
/**
* @since 0.9.2
* @category Instance
*/
export const ChainRec: ChainRec2<URI> = {
URI,
chainRec,
}
/**
* @since 0.9.2
* @category Instance
*/
export const MonadRec: MonadRec2<URI> = {
...Monad,
chainRec,
}
/**
* @since 0.9.2
* @category Instance
*/
export const FromReader: FR.FromReader2<URI> = {
fromReader: (reader) => (e) => R.sync(() => reader(e)),
}
/**
* @since 0.9.2
* @category Combinator
*/
export const raceW =
<E1, A>(a: Env<E1, A>) =>
<E2, B>(b: Env<E2, B>): Env<E1 & E2, A | B> =>
(e) =>
R.race(a(e))(b(e))
/**
* @since 0.9.2
* @category Combinator
*/
export const race =
<E, A>(a: Env<E, A>) =>
<B>(b: Env<E, B>): Env<E, A | B> =>
(e) =>
R.race(a(e))(b(e))
/**
* @since 0.9.2
* @category Instance
*/
export const Alt: Alt_.Alt2<URI> = {
...Functor,
alt:
<E, A>(snd: FN.Lazy<Env<E, A>>) =>
(fst: Env<E, A>) =>
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 <E1, A>(
snd: FN.Lazy<Env<E1, A>>,
) => <E2>(fst: Env<E2, A>) => Env<E1 & E2, A>
/**
* @since 0.9.2
* @category Combinator
*/
export const altAll = Alt_.altAll(Alt)
/**
* @since 0.9.2
* @category Constructor
*/
export const fromIO = fromReader as <A>(fa: IO.IO<A>) => Env<unknown, A>
/**
* @since 0.9.2
* @category Instance
*/
export const FromIO: FIO.FromIO2<URI> = {
URI,
fromIO,
}
/**
* @since 0.9.2
* @category Instance
*/
export const FromTask: FT.FromTask2<URI> = {
...FromIO,
fromTask: (task) => () => R.fromTask(task),
}
/**
* @since 0.9.2
* @category Constructor
*/
export const fromTask = FromTask.fromTask as <A, E = unknown>(fa: Task.Task<A>) => Env<E, A>
/**
* @since 0.9.2
* @category Constructor
*/
export const fromResume: <A, E = unknown>(resume: R.Resume<A>) => Env<E, A> = FN.constant
/**
* @since 0.9.2
* @category Combinator
*/
export const FromResume: FromResume2<URI> = {
fromResume,
}
/**
* @since 0.9.2
* @category Combinator
*/
export const useSome =
<E1>(provided: E1) =>
<E2, A>(env: Env<E1 & E2, A>): Env<E2, A> =>
(e) =>
env({ ...e, ...provided })
/**
* @since 0.9.2
* @category Combinator
*/
export const provideSome =
<E1>(provided: E1) =>
<E2, A>(env: Env<E1 & E2, A>): Env<E2, A> =>
(e) =>
env({ ...provided, ...e })
/**
* @since 0.9.2
* @category Combinator
*/
export const useAll =
<E1>(provided: E1) =>
<A>(env: Env<E1, A>): Env<unknown, A> =>
() =>
env(provided)
/**
* @since 0.9.2
* @category Combinator
*/
export const provideAll =
<E1>(provided: E1) =>
<A>(env: Env<E1, A>): Env<unknown, A> =>
(e) =>
env({ ...provided, ...((e as any) ?? {}) })
/**
* @since 0.9.2
* @category Instance
*/
export const UseSome: P.UseSome2<URI> = {
useSome,
}
/**
* @since 0.9.2
* @category Instance
*/
export const UseAll: P.UseAll2<URI> = {
useAll,
}
/**
* @since 0.9.2
* @category Instance
*/
export const ProvideSome: P.ProvideSome2<URI> = {
provideSome,
}
/**
* @since 0.9.2
* @category Instance
*/
export const ProvideAll: P.ProvideAll2<URI> = {
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<URI> = {
useSome,
useAll,
provideSome,
provideAll,
}
/**
* @since 0.9.2
* @category Constructor
*/
export const Do: Env<unknown, {}> = 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 <N extends string, A, E1, B>(
name: Exclude<N, keyof A>,
f: (a: A) => Env<E1, B>,
) => <E2>(
ma: Env<E2, A>,
) => Env<E1 & E2, { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }>
/**
* @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: <R, E, A>(f: (r: R) => Env<E, A>) => Env<E & R, A> = 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)(<E, A>(x: Env<E, A>) => x)
/**
* @since 0.9.2
* @category Combinator
*/
export const zipW = zip as unknown as <A extends ReadonlyArray<Env<any, any>>>(
envs: A,
) => Env<Intersect<{ [K in keyof A]: RequirementsOf<A[K]> }>, { [K in keyof A]: ValueOf<A[K]> }>
/**
* @since 0.9.2
* @category Combinator
*/
export const combineAll = <A extends ReadonlyArray<Env<any, any>>>(...envs: A) => zipW(envs)
/**
* @since 0.11.0
* @category Combinator
*/
export const combineStruct = <Props extends Readonly<Record<string, Env<any, any>>>>(
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<Props[K]> }),
)
/**
* @since 0.9.2
* @category Execution
*/
export const runWith =
<A>(f: (value: A) => Disposable) =>
<E>(requirements: E) =>
(env: Env<E, A>): Disposable =>
FN.pipe(requirements, env, R.run(f))
/**
* @since 0.9.2
* @category Execution
*/
export const execWith = runWith<any>(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 =
<F extends (...args: readonly any[]) => Env<any, any>>() =>
<K extends PropertyKey>(
key: K,
): {
(...args: ArgsOf<F>): Env<
RequirementsOf<ReturnType<F>> & { readonly [_ in K]: F },
ValueOf<ReturnType<F>>
>
readonly key: K
} => {
function operation(
...args: ArgsOf<F>
): Env<RequirementsOf<ReturnType<F>> & { readonly [_ in K]: F }, ValueOf<ReturnType<F>>> {
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 = <Args extends readonly any[], E, A>(envK: (...args: Args) => Env<E, A>) =>
FN.pipe(
ask<E>(),
map(
(e) =>
(...args: Args) =>
FN.pipe(e, envK(...args)),
),
)