UNPKG

@typed/fp

Version:

Data Structures and Resources for fp-ts

616 lines (549 loc) 13.6 kB
/** * `Ref` is an abstraction for managing data-driven applications using [Env](./Env.ts.md). * @since 0.11.0 */ import * as Ap from 'fp-ts/Apply' import * as B from 'fp-ts/boolean' import { EqStrict } from 'fp-ts/Eq' import { flow, pipe } from 'fp-ts/function' import { Functor3 } from 'fp-ts/Functor' import { IO } from 'fp-ts/IO' import { concatAll } from 'fp-ts/Monoid' import { Profunctor3 } from 'fp-ts/Profunctor' import { Reader } from 'fp-ts/Reader' import * as RA from 'fp-ts/ReadonlyArray' import { Semigroupoid3 } from 'fp-ts/Semigroupoid' import { Task } from 'fp-ts/Task' import { U } from 'ts-toolbelt' import * as E from './Env' import * as EO from './EnvOption' import * as FKV from './FromKV' import { Intersect } from './HKT' import * as KV from './KV' import * as O from './Option' import * as P from './Provide' import * as RS from './ReaderStream' import * as RSO from './ReaderStreamOption' import { Resume } from './Resume' import * as S from './struct' const allBooleans = E.map(concatAll(B.MonoidAll)) /** * @since 0.11.0 * @category Model */ export interface Ref<E, I, O = I> { readonly get: E.Env<E, O> readonly has: E.Env<E, boolean> readonly set: (input: I) => E.Env<E, O> readonly update: <E2>(f: (value: O) => E.Env<E2, I>) => E.Env<E & E2, O> readonly remove: E.Env<E, O.Option<O>> readonly values: RS.ReaderStream<E, O.Option<O>> } /** * @since 0.11.0 * @category Type-level */ export type RequirementsOf<A> = [A] extends [Ref<infer R, any, any>] ? R : never /** * @since 0.11.0 * @category Type-level */ export type InputOf<A> = [A] extends [Ref<any, infer R, any>] ? R : never /** * @since 0.11.0 * @category Type-level */ export type OutputOf<A> = [A] extends [Ref<any, any, infer R>] ? R : never /** * @since 0.11.0 * @category URI */ export const URI = '@typed/fp/Ref' /** * @since 0.11.0 * @category URI */ export type URI = typeof URI declare module 'fp-ts/HKT' { export interface URItoKind2<E, A> { [URI]: Ref<E, A> } export interface URItoKind3<R, E, A> { [URI]: Ref<R, E, A> } } declare module './HKT' { export interface URItoVariance { [URI]: V<R, Contravariant> } } /** * @since 0.11.0 * @category Constructor */ export const fromKV = <K, E, A>(kv: KV.KV<K, E, A>): Ref<E & KV.Env, A> & KV.KV<K, E, A> => ({ ...kv, get: KV.get(kv), has: KV.has(kv), set: KV.set(kv), update: KV.update(kv), remove: KV.remove(kv), values: KV.listenToValues(kv), }) /** * @since 0.11.0 * @category Constructor */ export const kv = flow(KV.make, fromKV) /** * @since 0.12.0 * @category Instance Constructor */ export const FromKV: FKV.FromKV2<URI, KV.Env> = { fromKV, } /** * @since 0.11.0 * @category Combinator */ export const map = <A, B>(f: (value: A) => B) => <E, I>(ref: Ref<E, I, A>): Ref<E, I, B> => ({ get: pipe(ref.get, E.map(f)), has: ref.has, set: flow(ref.set, E.map(f)), update: (g) => pipe(ref.get, E.map(f), E.chainW(g), E.chainW(ref.set), E.map(f)), remove: pipe(ref.remove, EO.map(f)), values: pipe(ref.values, RSO.map(f)), }) /** * @since 0.11.0 * @category Instance */ export const Functor: Functor3<URI> = { map, } /** * @since 0.11.0 * @category Combinator */ export const local = <A, B>(f: (value: A) => B) => <E, C>(ref: Ref<E, B, C>): Ref<E, A, C> => ({ get: ref.get, has: ref.has, set: flow(f, ref.set), update: (g) => ref.update(flow(g, E.map(f))), remove: ref.remove, values: ref.values, }) /** * @since 0.12.2 * @category Combinator */ export const localE = <A, E1, B>(f: (value: A) => E.Env<E1, B>) => <E2, C>(ref: Ref<E2, B, C>): Ref<E1 & E2, A, C> => ({ get: ref.get, has: ref.has, set: flow(f, E.chainW(ref.set)), update: (g) => ref.update(flow(g, E.chainW(f))), remove: ref.remove, values: ref.values, }) /** * @since 0.11.0 * @category Combinator */ export const promap = <B, A, C, D>(f: (value: B) => A, g: (value: C) => D) => <E>(ref: Ref<E, A, C>): Ref<E, B, D> => pipe(ref, local(f), map(g)) /** * @since 0.12.2 * @category Combinator */ export const promapE = <B, E1, A, C, E2, D>(f: (value: B) => E.Env<E1, A>, g: (value: C) => E.Env<E2, D>) => <E3>(ref: Ref<E3, A, C>): Ref<E1 & E2 & E3, B, D> => pipe(ref, localE(f), chainEnvK(g)) /** * @since 0.11.0 * @category Instance */ export const Profunctor: Profunctor3<URI> = { map, promap, } /** * @since 0.11.0 * @category Combinator */ export const ap = <E1, I, A>(fa: Ref<E1, I, A>) => <E2, B>(fab: Ref<E2, I, (value: A) => B>): Ref<E1 & E2, I, B> => { const get = pipe(fab.get, E.apW(fa.get)) const set = (i: I) => pipe(i, fab.set, E.apW(fa.set(i))) return { get, has: pipe([], E.of, E.apTW(fa.has), E.apTW(fab.has), allBooleans), set: (i) => pipe(i, fab.set, E.apW(fa.set(i))), update: (f) => pipe(get, E.chainW(f), E.chainW(set)), remove: pipe(fab.remove, EO.apW(fa.remove)), values: pipe(fab.values, RSO.apW(fa.values)), } } /** * @since 0.11.0 * @category Instance */ export const Apply: Ap.Apply3<URI> = { map, ap, } /** * @since 0.11.0 * @category Combinator */ export const apFirst = Ap.apFirst(Apply) /** * @since 0.11.0 * @category Combinator */ export const apFirstW = apFirst as <R1, E, B>( second: Ref<R1, E, B>, ) => <R2, A>(first: Ref<R2, E, A>) => Ref<R1 & R2, E, A> /** * @since 0.11.0 * @category Combinator */ export const apS = Ap.apS(Apply) /** * @since 0.11.0 * @category Combinator */ export const apSW = apS as <N extends string, A, R1, E, B>( name: Exclude<N, keyof A>, fb: Ref<R1, E, B>, ) => <R2>( fa: Ref<R2, E, A>, ) => Ref<R1 & R2, E, { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }> /** * @since 0.11.0 * @category Combinator */ export const apSecond = Ap.apSecond(Apply) /** * @since 0.11.0 * @category Combinator */ export const apSecondW = apSecond as <R1, E, B>( second: Ref<R1, E, B>, ) => <R2, A>(first: Ref<R2, E, A>) => Ref<R1 & R2, E, B> /** * @since 0.11.0 * @category Combinator */ export const apT = Ap.apT(Apply) /** * @since 0.11.0 * @category Combinator */ export const apTW = apT as <R1, E, B>( fb: Ref<R1, E, B>, ) => <R2, A extends readonly unknown[]>(fas: Ref<R2, E, A>) => Ref<R1 & R2, E, readonly [...A, B]> /** * @since 0.11.0 * @category Typeclass Constructor */ export const getApplySemigroup = Ap.getApplySemigroup(Apply) /** * @since 0.11.0 * @category Combinator */ export const compose = <E2, A, O>(second: Ref<E2, A, O>) => <E1, I>(first: Ref<E1, I, A>): Ref<E1 & E2, I, O> => ({ get: pipe(second.get, E.apFirstW(first.get)), // Ensure both exist has: pipe(first.has, E.tupled, E.apTW(second.has), allBooleans), set: flow(first.set, E.chainW(second.set)), update: (f) => pipe(second.get, E.chainW(f), E.chainW(first.set), E.chainW(second.set)), remove: pipe(second.remove, E.apFirstW(first.remove)), // Remove both in parallel values: pipe( second.values, RS.mergeFirst( // Replicate all values into the second pipe( first.values, RS.chainFirstEnvK(O.matchW(() => second.remove, flow(second.set, EO.fromEnv))), ), ), RS.skipRepeatsWith(O.getEq(EqStrict)), // Avoid sending duplicates ), }) /** * @since 0.11.0 * @category Instance */ export const Semigroupoid: Semigroupoid3<URI> = { compose, } /** * @since 0.11.0 * @category Combinator */ export const provideSome = <E1>(provided: E1) => <E2, A, B>(ref: Ref<E1 & E2, A, B>): Ref<E2, A, B> => { return { get: pipe(ref.get, E.provideSome(provided)), has: pipe(ref.has, E.provideSome(provided)), set: flow(ref.set, E.provideSome(provided)), update: flow(ref.update, E.provideSome(provided)), remove: pipe(ref.remove, E.provideSome(provided)), values: pipe(ref.values, RS.provideSome(provided)), } } /** * @since 0.11.0 * @category Combinator */ export const provideAll: <E>(provided: E) => <A, B>(ref: Ref<E, A, B>) => Ref<unknown, A, B> = provideSome /** * @since 0.11.0 * @category Combinator */ export const useSome = <E1>(provided: E1) => <E2, A, B>(ref: Ref<E1 & E2, A, B>): Ref<E2, A, B> => { return { get: pipe(ref.get, E.useSome(provided)), has: pipe(ref.has, E.useSome(provided)), set: flow(ref.set, E.useSome(provided)), update: flow(ref.update, E.useSome(provided)), remove: pipe(ref.remove, E.useSome(provided)), values: pipe(ref.values, RS.useSome(provided)), } } /** * @since 0.11.0 * @category Combinator */ export const useAll: <E1>(provided: E1) => <A, B>(ref: Ref<E1, A, B>) => Ref<unknown, A, B> = useSome /** * @since 0.11.0 * @category Instance */ export const UseSome: P.UseSome3<URI> = { useSome, } /** * @since 0.11.0 * @category Instance */ export const UseAll: P.UseAll3<URI> = { useAll, } /** * @since 0.11.0 * @category Instance */ export const ProvideSome: P.ProvideSome3<URI> = { provideSome, } /** * @since 0.11.0 * @category Instance */ export const ProvideAll: P.ProvideAll3<URI> = { provideAll, } /** * @since 0.11.0 * @category Instance */ export const Provide: P.Provide3<URI> = { useSome, useAll, provideSome, provideAll, } /** * @since 0.11.0 * @category Combinator */ export function chainEnvK<A, E1, B>(f: (value: A) => E.Env<E1, B>) { return <E2, I>(ref: Ref<E2, I, A>): Ref<E1 & E2, I, B> => ({ get: pipe(ref.get, E.chainW(f)), has: ref.has, set: flow(ref.set, E.chainW(f)), update: (g) => pipe(flow(f, E.chainW(g)), ref.update, E.chainW(f)), remove: pipe(ref.remove, EO.chainEnvK(f)), values: pipe(ref.values, RSO.chainEnvK(f)), }) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstEnvK<A, E1, B>(f: (value: A) => E.Env<E1, B>) { return <E2, I>(ref: Ref<E2, I, A>): Ref<E1 & E2, I, A> => ({ get: pipe(ref.get, E.chainFirstW(f)), has: ref.has, set: flow(ref.set, E.chainFirstW(f)), update: flow(ref.update, E.chainFirstW(f)), remove: pipe(ref.remove, EO.chainFirstEnvK(f)), values: pipe(ref.values, RSO.chainFirstEnvK(f)), }) } /** * @since 0.11.0 * @category Combinator */ export function chainIOK<A, B>(f: (value: A) => IO<B>) { return pipe(f, E.fromIOK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstIOK<A, B>(f: (value: A) => IO<B>) { return pipe(f, E.fromIOK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainReaderK<A, E1, B>(f: (value: A) => Reader<E1, B>) { return pipe(f, E.fromReaderK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstReaderK<A, E1, B>(f: (value: A) => Reader<E1, B>) { return pipe(f, E.fromReaderK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainResumeK<A, B>(f: (value: A) => Resume<B>) { return pipe(f, E.fromResumeK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstResumeK<A, B>(f: (value: A) => Resume<B>) { return pipe(f, E.fromResumeK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainTaskK<A, B>(f: (value: A) => Task<B>) { return pipe(f, E.fromTaskK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstTaskK<A, B>(f: (value: A) => Task<B>) { return pipe(f, E.fromTaskK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function struct<S extends AnyRefStruct>( properties: S, ): Ref<RefStructEnv<S>, RefStructInput<S>, RefStructOutput<S>> { const entries = Object.entries(properties) const zipAssign = flow( E.zipW, E.map((props) => Object.assign({}, ...props) as RefStructOutput<S>), ) const get = pipe( entries, RA.map(([k, ref]) => pipe( ref.get, E.map((v) => S.make(k, v)), ), ), zipAssign, ) const has = pipe( Object.values(properties), RA.map((ref) => ref.has), E.zipW, allBooleans, ) const set = (i: RefStructInput<S>) => pipe( i, Object.entries, RA.map(([k, v]) => pipe( v, properties[k].set, E.map((v) => S.make(k, v)), ), ), zipAssign, ) const remove = pipe( entries, RA.map(([k, ref]) => pipe( ref.remove, EO.map((v) => S.make(k, v)), ), ), E.zipW, E.map( flow( O.traverseReadonlyArrayWithIndex((_, a) => a), O.map((xs) => Object.assign({}, ...xs) as RefStructOutput<S>), ), ), ) const values = pipe( RS.combineAll( ...pipe( entries, RA.map(([k, ref]) => pipe( ref.values, RSO.map((v) => S.make(k, v)), ), ), ), ), RS.map( flow( O.traverseReadonlyArrayWithIndex((_, a) => a), O.map((xs) => Object.assign({}, ...xs) as RefStructOutput<S>), ), ), ) return { get, has, set, update: (f) => pipe(get, E.chainW(f), E.chainW(set)), remove, values, } } type AnyRefStruct = Readonly<Record<string, Ref<any, any, any>>> type RefStructEnv<A extends AnyRefStruct> = Intersect<U.ListOf<RequirementsOf<A[keyof A]>>> type RefStructInput<S extends AnyRefStruct> = { readonly [K in keyof S]: InputOf<S[K]> } type RefStructOutput<S extends AnyRefStruct> = { readonly [K in keyof S]: OutputOf<S[K]> }