/**
* 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);