/** * The State module contains the State structure. The purpose of State is to * have modifiable date in an immutable workflow. This structure must preserve * purity, so that subsequent executions of a state workflow with the same * initial conditions produce the exact same results. * * @module State * @since 2.0.0 */ import type { InOut, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Combinable } from "./combinable.ts"; import type { Bind, Flatmappable, Tap } from "./flatmappable.ts"; import type { Initializable } from "./initializable.ts"; import type { BindTo, Mappable } from "./mappable.ts"; import type { Wrappable } from "./wrappable.ts"; import { flow } from "./fn.ts"; import { createBind, createTap } from "./flatmappable.ts"; import { createBindTo } from "./mappable.ts"; /** * The State type represents the core State structure. The input/output * variable E is invariant, and the output variable A is covariant. * * @since 2.0.0 */ export type State = (e: E) => [A, E]; /** * Specifies State as a Higher Kinded Type, with covariant parameter A in the * 0th index of any substitutions and invariant parameter E in the 0th parameter * of any substitutions. * * @since 2.0.0 */ export interface KindState extends Kind { readonly kind: State, Out>; } /** * An internal optimization over State identity * * @since 2.0.0 */ // deno-lint-ignore no-explicit-any const _identity: State = (s) => [s, s]; /** * Construct a trivial State from values E and A. * * @example * ```ts * import * as S from "./state.ts"; * * const state = S.state(1, 2); * * const result = state(10); // [2, 1] * ``` * * @since 2.0.0 */ export function state(a: A, e: E): State { return () => [a, e]; } /** * An instance of Id that makes the State structure a Category. This is often * used at the beginning of a State workflow to specify the type of data within * a State structure. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const num = pipe( * S.id(), * S.map(n => n + 1), * ); * * const result = num(1); // [2, 1] * ``` * * @since 2.0.0 */ export function id(): State { return _identity; } /** * Construct a State from a function E => A. * * @example * ```ts * import * as S from "./state.ts"; * * const length = S.gets((s: string) => s.length); * * const result = length("Hello World"); // [11, "Hello World"] * ``` * * @since 2.0.0 */ export function gets(fea: (e: E) => A): State { return (e) => [fea(e), e]; } /** * Construct a State from a static state value E. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const state = pipe( * S.id(), * S.flatmap(n => pipe(S.id(), S.map(m => m + n))), * ); * * const result = state(100); // [2, 1] * ``` * * @since 2.0.0 */ export function put(e: E): State { return () => [undefined, e]; } /** * Construct a State from a function E => E. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const state = S.modify((n: number) => n + 100); * * const result = state(1); // [undefined, 101] * ``` * * @since 2.0.0 */ export function modify(fee: (e: E) => E): State { return (e) => [undefined, fee(e)]; } /** * Construct a State from a static value A. * * @example * ```ts * import * as S from "./state.ts"; * * const state = S.wrap(1); * * const result = state(null); // [1, null] * ``` * * @since 2.0.0 */ export function wrap(a: A): State { return (e) => [a, e]; } /** * Map over the covariant value A in State. * * @example * ```ts * import * as S from "./state.ts"; * import * as A from "./array.ts"; * import { pipe } from "./fn.ts"; * * const work = pipe( * S.id(), * S.map(n => A.range(n)), * ); * * const result1 = work(1); // [[0], 1] * const result2 = work(3); // [[0, 1, 2], 3] * ``` * * @since 2.0.0 */ export function map( fai: (a: A) => I, ): (ta: State) => State { return (ta) => flow(ta, ([a, b]) => [fai(a), b]); } /** * Apply the A value of State to the (a: A) => I value of * State I>, producing a State. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const work = pipe( * S.id(), * S.map(s => (n: number) => s.repeat(n)), * S.apply(S.gets(s => s.length)) * ); * * const result1 = work("Hi"); // ["HiHi", "Hi"] * const result2 = work("Hello"); * // ["HelloHelloHelloHelloHello", "Hello"] * ``` * * @since 2.0.0 */ export function apply( ua: State, ): (ufai: State I>) => State { return (ufai) => (s1) => { const [fai, s2] = ufai(s1); const [a, s3] = ua(s2); return [fai(a), s3]; }; } /** * Pass the A value in a State into a function (a: A) => State. This * results in a new State. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const state = pipe( * S.id(), * S.flatmap(n => S.wrap(n + 1)), * ); * * const result1 = state(1); // [2, 1] * const result2 = state(2); // [3, 2] * ``` * * @since 2.0.0 */ export function flatmap( fati: (a: A) => State, ): (ta: State) => State { return (ta) => flow(ta, ([a, s]) => fati(a)(s)); } /** * Extract the result value A by executing State with an S value. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const result = pipe( * S.id(), * S.map(n => n + 1), * S.evaluate(10), * ); // 11 * ``` * * @since 2.0.0 */ export function evaluate(s: S): (ta: State) => A { return (ta) => ta(s)[0]; } /** * Extract the ending state value S by executing State with an S value. * * @example * ```ts * import * as S from "./state.ts"; * import { pipe } from "./fn.ts"; * * const result = pipe( * S.id(), * S.map(n => n + 1), * S.execute(10), * ); // 10 * ``` * * @since 2.0.0 */ export function execute(s: S): (ta: State) => S { return (ta) => ta(s)[1]; } /** * @since 2.0.0 */ export function getCombinableState( CE: Combinable, CA: Combinable, ): Combinable> { return { combine: (second) => (first) => (e) => { const [fa, fe] = first(e); const [sa, se] = second(e); return [CA.combine(sa)(fa), CE.combine(se)(fe)]; }, }; } /** * @since 2.0.0 */ export function getInitializableState( IE: Initializable, IA: Initializable, ): Initializable> { return { init: () => (e) => [IA.init(), IE.combine(e)(IE.init())], ...getCombinableState(IE, IA), }; } /** * @since 2.0.0 */ export const ApplicableState: Applicable = { apply, map, wrap, }; /** * The canonical implementation of Flatmappable for State. It contains * the methods wrap, apply, map, join, and flatmap. * * @since 2.0.0 */ export const FlatmappableState: Flatmappable = { apply, flatmap, map, wrap, }; /** * @since 2.0.0 */ export const MappableState: Mappable = { map }; /** * @since 2.0.0 */ export const WrappableState: Wrappable = { wrap }; /** * @since 2.0.0 */ export const tap: Tap = createTap(FlatmappableState); /** * @since 2.0.0 */ export const bind: Bind = createBind(FlatmappableState); /** * @since 2.0.0 */ export const bindTo: BindTo = createBindTo(MappableState);