/** * Flatmappable is a structure that allows a function that returns the concrete * structure to be applied to the value inside of the same type of concrete * structure. The resultant nested structure is then flattened. * * @module Flatmappable * @since 2.0.0 */ import type { $, Hold, Kind } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; /** * A Flatmappable structure. * * @since 2.0.0 */ export interface Flatmappable< U extends Kind, > extends Applicable, Hold { readonly flatmap: ( fati: (a: A) => $, ) => ( ta: $, ) => $; } /** * The return type for the createTap function on Flatmappable. Useful to reduce * type inference in documentation. * * @since 2.0.0 */ export type Tap = ( fn: (value: A) => void, ) => ( ua: $, ) => $; /** * Create a tap function for a structure with instances of Wrappable and * Flatmappable. A tap function allows one to break out of the functional * codeflow. It is generally not advised to use tap for code flow but to * consider an escape hatch to do things like tracing or logging. * * @example * ```ts * import { createTap } from "./flatmappable.ts"; * import * as O from "./option.ts"; * import { pipe } from "./fn.ts"; * * const tap = createTap(O.FlatmappableOption); * const option = O.some(5); * * const result = pipe( * option, * tap(value => console.log(`Processing: ${value}`)) * ); * * // Output: "Processing: 5" * console.log(result); // Some(5) * ``` * * @since 2.0.0 */ export function createTap( { wrap, flatmap }: Flatmappable, ): ( fn: (value: A) => void, ) => ( ua: $, ) => $ { return (fn) => flatmap((a) => { fn(a); return wrap(a); }); } // /** // * The return type for the createBind function on Flatmappable. Useful to reduce // * type inference in documentation. // * // * @since 2.0.0 // */ export type Bind = < N extends string, A, I, J = never, K = never, L = unknown, M = unknown, >( name: Exclude, faui: (a: A) => $, ) => ( ua: $, ) => $< U, [{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }, B | J, C | K], [D & L], [M] >; // /** // * Create a bind function for a structure with instances of Mappable and // * Flatmappable. A bind function allows one to flatmap into named fields in a // * struct, collecting values from the result of the flatmap in the order that // * they are completed. // * // * @example // * ```ts // * import { createBind } from "./flatmappable.ts"; // * import * as O from "./option.ts"; // * import { pipe } from "./fn.ts"; // * // * const bind = createBind(O.FlatmappableOption); // * const option = O.some({ name: "John" }); // * // * const result = pipe( // * option, // * bind("age", user => O.some(30)) // * ); // * // * console.log(result); // Some({ name: "John", age: 30 }) // * ``` // * // * @since 2.0.0 // */ export function createBind( { flatmap, map }: Flatmappable, ): Bind { return < N extends string, A, I, J = never, K = never, L = unknown, M = unknown, >(name: Exclude, faui: (a: A) => $) => flatmap((a: A) => { const mapper = map((i: I) => { type Bound = { readonly [K in keyof A | N]: K extends keyof A ? A[K] : I; }; return Object.assign({}, a, { [name]: i }) as unknown as Bound; }); return mapper(faui(a)); }); } // /** // * Create a Flatmappable instance from wrap and flatmap functions. // * // * @example // * ```ts // * import type { Kind, Out } from "./kind.ts"; // * import { createFlatmappable } from "./flatmappable.ts"; // * import { pipe } from "./fn.ts"; // * // * // Create a Kind for Promise // * interface KindPromise extends Kind { // * readonly kind: Promise>; // * }; // * // * // Create an of and chain function for Promise // * const wrap = (a: A): Promise => Promise.resolve(a); // * const flatmap = (faui: (a: A) => Promise) => // * (ua: Promise): Promise => ua.then(faui); // * // * // Derive a Flatmappable for Promise // * const M = createFlatmappable({ wrap, flatmap }); // * // * const result = await pipe( // * M.wrap((n: number) => (m: number) => n + m), // * M.apply(M.wrap(1)), // * M.apply(M.wrap(1)), // * ); // 2 // * ``` // * // * @experimental // * // * @since 2.0.0 // */ export function createFlatmappable( { wrap, flatmap }: Pick, "wrap" | "flatmap">, ): Flatmappable { const result: Flatmappable = { wrap, apply: ((ua) => flatmap((fai) => result.map(fai)(ua))) as Flatmappable< U >["apply"], map: ((fai) => flatmap((a) => wrap(fai(a)))) as Flatmappable["map"], flatmap, }; return result; }