/** * This file contains the Datum algebraic data type. Datum represents an * optional value that has an additional notation for a pending state. * * @module Datum * @since 2.0.0 */ import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Combinable } from "./combinable.ts"; import type { Comparable } from "./comparable.ts"; import type { Either } from "./either.ts"; import type { Filterable } from "./filterable.ts"; import type { Bind, Flatmappable, Tap } from "./flatmappable.ts"; import type { Initializable } from "./initializable.ts"; import type { BindTo, Mappable } from "./mappable.ts"; import type { Option } from "./option.ts"; import type { Pair } from "./pair.ts"; import type { Predicate } from "./predicate.ts"; import type { Refinement } from "./refinement.ts"; import type { Showable } from "./showable.ts"; import type { Sortable } from "./sortable.ts"; import type { Traversable } from "./traversable.ts"; import type { Wrappable } from "./wrappable.ts"; import * as O from "./option.ts"; import { createBind, createTap } from "./flatmappable.ts"; import { createBindTo } from "./mappable.ts"; import { fromSort } from "./sortable.ts"; import { isNotNil } from "./nil.ts"; import { flow, handleThrow, identity, pipe } from "./fn.ts"; /** * @since 2.0.0 */ export type Initial = { readonly tag: "Initial"; }; /** * @since 2.0.0 */ export type Pending = { readonly tag: "Pending"; }; /** * @since 2.0.0 */ export type Refresh = { readonly tag: "Refresh"; readonly value: A; }; /** * @since 2.0.0 */ export type Replete = { readonly tag: "Replete"; readonly value: A; }; /** * @since 2.0.0 */ export type Datum = Initial | Pending | Refresh | Replete; /** * @since 2.0.0 */ export type None = Initial | Pending; /** * @since 2.0.0 */ export type Some = Refresh | Replete; /** * @since 2.0.0 */ export type Loading = Pending | Refresh; /** * @since 2.0.0 */ export interface KindDatum extends Kind { readonly kind: Datum>; } /** * @since 2.0.0 */ export const initial: Initial = { tag: "Initial" }; /** * @since 2.0.0 */ export const pending: Pending = { tag: "Pending" }; /** * @since 2.0.0 */ export function refresh(value: D): Datum { return ({ tag: "Refresh", value }); } /** * @since 2.0.0 */ export function replete(value: D): Datum { return ({ tag: "Replete", value }); } /** * @since 2.0.0 */ export function constInitial(): Datum { return initial; } /** * @since 2.0.0 */ export function constPending(): Datum { return pending; } /** * @since 2.0.0 */ export function fromNullable(a: A): Datum> { return isNotNil(a) ? replete(a) : initial; } /** * @since 2.0.0 */ export function tryCatch( fasr: (...as: AS) => A, ): (...as: AS) => Datum { return handleThrow( fasr, replete, constInitial, ); } /** * @since 2.0.0 */ export function toLoading(ta: Datum): Datum { return pipe( ta, match( constPending, constPending, refresh, refresh, ), ); } /** * @since 2.0.0 */ export function isInitial(ta: Datum): ta is Initial { return ta.tag === "Initial"; } /** * @since 2.0.0 */ export function isPending(ta: Datum): ta is Pending { return ta.tag === "Pending"; } /** * @since 2.0.0 */ export function isRefresh(ta: Datum): ta is Refresh { return ta.tag === "Refresh"; } /** * @since 2.0.0 */ export function isReplete(ta: Datum): ta is Replete { return ta.tag === "Replete"; } /** * @since 2.0.0 */ export function isNone(ta: Datum): ta is None { return isInitial(ta) || isPending(ta); } /** * @since 2.0.0 */ export function isSome(ta: Datum): ta is Some { return isRefresh(ta) || isReplete(ta); } /** * @since 2.0.0 */ export function isLoading(ta: Datum): ta is Loading { return isPending(ta) || isRefresh(ta); } /** * @since 2.0.0 */ export function match( onInitial: () => B, onPending: () => B, onReplete: (a: A) => B, onRefresh: (a: A) => B, ): (ua: Datum) => B { return (ua) => { switch (ua.tag) { case "Initial": return onInitial(); case "Pending": return onPending(); case "Refresh": return onRefresh(ua.value); case "Replete": return onReplete(ua.value); } }; } /** * @since 2.0.0 */ export function getOrElse(onNone: () => A): (ua: Datum) => A { return match(onNone, onNone, identity, identity); } /** * @since 2.0.0 */ export function wrap(a: A): Datum { return replete(a); } /** * @since 2.0.0 */ export function map(fai: (a: A) => I): (ta: Datum) => Datum { return match( constInitial, constPending, flow(fai, replete), flow(fai, refresh), ); } /** * @since 2.0.0 */ export function apply( ua: Datum, ): (ufai: Datum<(a: A) => I>) => Datum { switch (ua.tag) { case "Initial": return (ufai) => isLoading(ufai) ? pending : initial; case "Pending": return constPending; case "Replete": return (ufai) => isReplete(ufai) ? replete(ufai.value(ua.value)) : isRefresh(ufai) ? refresh(ufai.value(ua.value)) : isLoading(ufai) ? pending : initial; case "Refresh": return (ufai) => isSome(ufai) ? refresh(ufai.value(ua.value)) : pending; } } /** * @since 2.0.0 */ export function flatmap( fati: (a: A) => Datum, ): (ta: Datum) => Datum { return match( constInitial, constPending, fati, flow(fati, toLoading), ); } /** * @since 2.0.0 */ export function alt(tb: Datum): (ta: Datum) => Datum { return (ta) => isSome(ta) ? ta : tb; } /** * @since 2.0.0 */ export function fold( foao: (o: O, a: A) => O, o: O, ): (ta: Datum) => O { return (ta) => isSome(ta) ? foao(o, ta.value) : o; } /** * @since 2.0.0 */ export function exists(predicate: Predicate): (ua: Datum) => boolean { return (ua) => isSome(ua) && predicate(ua.value); } /** * @since 2.0.0 */ export function filter( refinement: Refinement, ): (ta: Datum) => Datum; export function filter( predicate: Predicate, ): (ta: Datum) => Datum; export function filter( predicate: Predicate, ): (ta: Datum) => Datum { const _exists = exists(predicate); return (ta) => _exists(ta) ? ta : isLoading(ta) ? pending : initial; } /** * @since 2.0.0 */ export function filterMap( fai: (a: A) => Option, ): (ua: Datum) => Datum { return (ua) => { if (isNone(ua)) { return ua; } const oi = fai(ua.value); if (isReplete(ua)) { return O.isNone(oi) ? initial : replete(oi.value); } else { return O.isNone(oi) ? pending : refresh(oi.value); } }; } /** * @since 2.0.0 */ export function partition( refinement: Refinement, ): (ua: Datum) => Pair, Datum>; export function partition( predicate: Predicate, ): (ua: Datum) => Pair, Datum>; export function partition( predicate: Predicate, ): (ua: Datum) => Pair, Datum> { return (ua) => { if (isNone(ua)) { return [ua, ua]; } if (predicate(ua.value)) { if (isReplete(ua)) { return [ua, initial]; } return [ua, pending]; } if (isReplete(ua)) { return [initial, ua]; } return [pending, ua]; }; } /** * @since 2.0.0 */ export function partitionMap( fai: (a: A) => Either, ): (ua: Datum) => Pair, Datum> { return (ua) => { if (isNone(ua)) { return [ua, ua]; } const result = fai(ua.value); if (isReplete(ua)) { return result.tag === "Right" ? [replete(result.right), initial] : [initial, replete(result.left)]; } else { return result.tag === "Right" ? [refresh(result.right), pending] : [pending, refresh(result.left)]; } }; } /** * @since 2.0.0 */ export function traverse( A: Applicable, ): ( favi: (a: A) => $, ) => (ta: Datum) => $, J, K], [L], [M]> { return (favi) => match( () => A.wrap(constInitial()), () => A.wrap(constPending()), (a) => pipe(favi(a), A.map(replete)), (a) => pipe(favi(a), A.map(refresh)), ); } /** * @since 2.0.0 */ export function getShowableDatum({ show }: Showable): Showable> { return ({ show: match( () => `Initial`, () => `Pending`, (a) => `Replete(${show(a)})`, (a) => `Refresh(${show(a)})`, ), }); } /** * @since 2.0.0 */ export function getCombinableDatum( S: Combinable, ): Combinable> { return ({ combine: (second) => match( () => second, () => toLoading(second), (v) => isSome(second) ? (isRefresh(second) ? refresh(S.combine(second.value)(v)) : replete(S.combine(second.value)(v))) : (isPending(second) ? refresh(v) : replete(v)), (v) => isSome(second) ? refresh(S.combine(second.value)(v)) : refresh(v), ), }); } /** * @since 2.0.0 */ export function getInitializableDatum( S: Initializable, ): Initializable> { return ({ init: constInitial, ...getCombinableDatum(S), }); } /** * @since 2.0.0 */ export function getComparableDatum(S: Comparable): Comparable> { return ({ compare: (b) => match( () => isInitial(b), () => isPending(b), (v) => isReplete(b) ? S.compare(b.value)(v) : false, (v) => isRefresh(b) ? S.compare(b.value)(v) : false, ), }); } /** * @since 2.0.0 */ export function getSortableDatum(O: Sortable): Sortable> { return fromSort((fst, snd) => pipe( fst, match( () => isInitial(snd) ? 0 : -1, () => isInitial(snd) ? 1 : isPending(snd) ? 0 : -1, (value) => isNone(snd) ? 1 : isReplete(snd) ? O.sort(value, snd.value) : -1, (value) => isRefresh(snd) ? O.sort(value, snd.value) : 1, ), ) ); } /** * @since 2.0.0 */ export const ApplicableDatum: Applicable = { apply, map, wrap, }; /** * @since 2.0.0 */ export const MappableDatum: Mappable = { map, }; /** * @since 2.0.0 */ export const FlatmappableDatum: Flatmappable = { apply, flatmap, map, wrap, }; /** * @since 2.0.0 */ export const TraversableDatum: Traversable = { map, fold, traverse, }; /** * @since 2.0.0 */ export const WrappableDatum: Wrappable = { wrap, }; /** * @since 2.0.0 */ export const FilterableDatum: Filterable = { filter, filterMap, partition, partitionMap, }; /** * @since 2.0.0 */ export const tap: Tap = createTap(FlatmappableDatum); /** * @since 2.0.0 */ export const bind: Bind = createBind(FlatmappableDatum); /** * @since 2.0.0 */ export const bindTo: BindTo = createBindTo(MappableDatum);