(
+ initialState: S,
+ ...cases: Array>
+): Reducer {
+ return (state = initialState, action) =>
+ cases.reduce((s, r) => r(s, action), state);
+}
+
+/**
+ * Generate a reducer that wraps a single DatumEither store value
+ *
+ * @since 2.1.0
+ */
+export function asyncReducerFactory(
+ action: AsyncActionCreators
,
+ lens: Lens>,
+): Reducer {
+ return reducerFn(
+ caseFn(action.pending, pipe(lens, O.modify(D.toLoading))),
+ caseFn(
+ action.success,
+ (s, a) => pipe(lens, O.replace(DE.success(a.value.result)))(s),
+ ),
+ caseFn(
+ action.failure,
+ (s, a) => pipe(lens, O.replace(DE.failure(a.value.error)))(s),
+ ),
+ );
+}
+
+/**
+ * Filters actions by first section of action type to bypass sections of the store
+ *
+ * @since 7.1.0
+ */
+export const filterReducer = (
+ match: string,
+ reducer: Reducer,
+): Reducer =>
+(state, action) =>
+ action.type.startsWith(match) ? reducer(state, action) : state;
diff --git a/datum_either.ts b/datum_either.ts
new file mode 100644
index 0000000..305c89a
--- /dev/null
+++ b/datum_either.ts
@@ -0,0 +1,506 @@
+/**
+ * The DatumEither datastructure represents an asynchronous operation that can
+ * fail. At its heart it is implemented as `() => Promise>`. This
+ * thunk makes it a performant but lazy operation at the expense of stack
+ * safety.
+ *
+ * @module DatumEither
+ * @since 2.1.0
+ */
+
+import type { Kind, Out } from "./kind.ts";
+import type { Applicable } from "./applicable.ts";
+import type { Bimappable } from "./bimappable.ts";
+import type { Bind, Flatmappable } from "./flatmappable.ts";
+import type { BindTo, Mappable } from "./mappable.ts";
+import type { Combinable } from "./combinable.ts";
+import type { Comparable } from "./comparable.ts";
+import type { Datum } from "./datum.ts";
+import type { Either } from "./either.ts";
+import type { Failable, Tap } from "./failable.ts";
+import type { Initializable } from "./initializable.ts";
+import type { Option } from "./option.ts";
+import type { Showable } from "./showable.ts";
+import type { Sortable } from "./sortable.ts";
+import type { Wrappable } from "./wrappable.ts";
+
+import * as E from "./either.ts";
+import * as D from "./datum.ts";
+import { none, some } from "./option.ts";
+import { apply as createApply } from "./applicable.ts";
+import { createBind } from "./flatmappable.ts";
+import { createBindTo } from "./mappable.ts";
+import { createTap } from "./failable.ts";
+import { handleThrow, pipe } from "./fn.ts";
+import { isNotNil } from "./nil.ts";
+
+/**
+ * The DatumEither type can best be thought of as an asynchronous function that
+ * returns an `Either`. ie. `async () => Promise>`. This
+ * forms the basis of most Promise based asynchronous communication in
+ * TypeScript.
+ *
+ * @since 2.1.0
+ */
+export type DatumEither = Datum>;
+
+/**
+ * @since 2.0.0
+ */
+export type Success = D.Refresh> | D.Replete>;
+
+/**
+ * @since 2.0.0
+ */
+export type Failure = D.Refresh> | D.Replete>;
+
+/**
+ * Specifies DatumEither as a Higher Kinded Type, with covariant
+ * parameter A corresponding to the 0th index of any substitutions and covariant
+ * parameter B corresponding to the 1st index of any substitutions.
+ *
+ * @since 2.1.0
+ */
+export interface KindDatumEither extends Kind {
+ readonly kind: DatumEither, Out>;
+}
+
+/**
+ * Constructs a DatumEither from a value and wraps it in an inner *Left*
+ * traditionally signaling a failure.
+ *
+ * @example
+ * ```ts
+ * import * as DE from "./datum_either.ts";
+ *
+ * const left = DE.left(1);
+ * ```
+ *
+ * @since 2.1.0
+ */
+export function left(left: B): DatumEither {
+ return D.wrap(E.left(left));
+}
+
+/**
+ * Constructs a DatumEither from a value and wraps it in an inner *Right*
+ * traditionally signaling a successful computation.
+ *
+ * @example
+ * ```ts
+ * import * as DE from "./datum_either.ts";
+ *
+ * const right = DE.right(1);
+ * ```
+ *
+ * @since 2.1.0
+ */
+export function right(right: A): DatumEither {
+ return D.wrap(E.right(right));
+}
+
+/**
+ * @since 2.1.0
+ */
+export const initial: D.Initial = { tag: "Initial" };
+
+/**
+ * @since 2.1.0
+ */
+export const pending: D.Pending = { tag: "Pending" };
+
+/**
+ * @since 2.1.0
+ */
+export function success(
+ value: A,
+ refresh: boolean = false,
+): DatumEither {
+ return refresh ? D.refresh(E.right(value)) : D.replete(E.right(value));
+}
+
+/**
+ * @since 2.1.0
+ */
+export function failure(
+ value: B,
+ refresh: boolean = false,
+): DatumEither {
+ return refresh ? D.refresh(E.left(value)) : D.replete(E.left(value));
+}
+
+/**
+ * @since 2.1.0
+ */
+export function constInitial(): DatumEither {
+ return initial;
+}
+
+/**
+ * @since 2.1.0
+ */
+export function constPending(): DatumEither {
+ return pending;
+}
+
+/**
+ * @since 2.1.0
+ */
+export function fromNullable(a: A): DatumEither> {
+ return isNotNil(a) ? success(a as NonNullable) : initial;
+}
+/**
+ * @since 2.1.0
+ */
+export function match(
+ onInitial: () => O,
+ onPending: () => O,
+ onFailure: (b: B, refresh: boolean) => O,
+ onSuccess: (a: A, refresh: boolean) => O,
+): (ua: DatumEither) => O {
+ return (ua) => {
+ switch (ua.tag) {
+ case "Initial":
+ return onInitial();
+ case "Pending":
+ return onPending();
+ case "Refresh":
+ return pipe(
+ ua.value,
+ E.match((b) => onFailure(b, true), (a) => onSuccess(a, true)),
+ );
+ case "Replete":
+ return pipe(
+ ua.value,
+ E.match((b) => onFailure(b, false), (a) => onSuccess(a, false)),
+ );
+ }
+ };
+}
+
+/**
+ * Wraps a Datum of A in a try-catch block which upon failure returns B instead.
+ * Upon success returns a *Right* and *Left* for a failure.
+ *
+ * @since 2.1.0
+ */
+export function tryCatch(
+ fasr: (...as: AS) => A,
+ onThrow: (e: unknown, as: AS) => B,
+): (...as: AS) => DatumEither {
+ return handleThrow(
+ fasr,
+ (a) => right(a),
+ (e: unknown, as) => left(onThrow(e, as)),
+ );
+}
+
+/**
+ * @since 2.0.0
+ */
+export function isSuccess(ua: DatumEither): ua is Success {
+ return D.isSome(ua) && E.isRight(ua.value);
+}
+
+/**
+ * @since 2.0.0
+ */
+export function isFailure(ua: DatumEither): ua is Failure {
+ return D.isSome(ua) && E.isLeft(ua.value);
+}
+
+/**
+ * Lift an always succeeding async computation (Datum) into a DatumEither.
+ *
+ * @example
+ * ```ts
+ * import * as DE from "./datum_either.ts";
+ * import * as D from "./datum.ts";
+ *
+ * const value = DE.fromDatum(D.wrap(1));
+ * ```
+ *
+ * @since 2.1.0
+ */
+export function fromDatum(ta: Datum): DatumEither {
+ return pipe(ta, D.map(E.right));
+}
+
+/**
+ * Lifts an Either into a DatumEither.
+ *
+ * @example
+ * ```ts
+ * import * as DE from "./datum_either.ts";
+ * import * as E from "./either.ts";
+ *
+ * const value1 = DE.fromEither(E.right(1));
+ * const value2 = DE.fromEither(E.left("Error!"));
+ * ```
+ *
+ * @since 2.1.0
+ */
+export function fromEither(ta: Either): DatumEither {
+ return D.wrap(ta);
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getSuccess(ua: DatumEither): Option {
+ return isSuccess(ua) ? some(ua.value.right) : none;
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getFailure(ua: DatumEither): Option {
+ return isFailure(ua) ? some(ua.value.left) : none;
+}
+
+/**
+ * Construct an DatumEither from a value D.
+ *
+ * @example
+ * ```ts
+ * import * as DE from "./datum_either.ts";
+ *
+ * const value = DE.wrap(1);
+ * ```
+ *
+ * @since 2.1.0
+ */
+export function wrap(a: A): DatumEither {
+ return right(a);
+}
+
+/**
+ * Construct an DatumEither from a value B.
+ *
+ * @example
+ * ```ts
+ * import * as DE from "./datum_either.ts";
+ *
+ * const value = DE.fail("Error!");
+ * ```
+ *
+ * @since 2.1.0
+ */
+export function fail(b: B): DatumEither {
+ return left(b);
+}
+
+/**
+ * Map a function over the *Right* side of a DatumEither
+ *
+ * @since 2.1.0
+ */
+export function map(
+ fai: (a: A) => I,
+): (ta: DatumEither) => DatumEither {
+ return (ta) => pipe(ta, D.map(E.map(fai)));
+}
+
+/**
+ * Map a function over the *Left* side of a DatumEither
+ *
+ * @since 2.1.0
+ */
+export function mapSecond(
+ fbj: (b: B) => J,
+): (ta: DatumEither) => DatumEither {
+ return (ta) => pipe(ta, D.map(E.mapSecond(fbj)));
+}
+
+/**
+ * TODO: revisit createApply to align types.
+ *
+ * @since 2.1.0
+ */
+const _apply: Applicable["apply"] = createApply(
+ D.ApplicableDatum,
+ E.ApplicableEither,
+) as Applicable["apply"];
+
+/**
+ * Apply an argument to a function under the *Right* side.
+ *
+ * @since 2.1.0
+ */
+export function apply(
+ ua: DatumEither,
+): (ufai: DatumEither I>) => DatumEither {
+ return _apply(ua);
+}
+
+/**
+ * Chain DatumEither based computations together in a pipeline
+ *
+ * @since 2.1.0
+ */
+export function flatmap(
+ fati: (a: A) => DatumEither,
+): (ua: DatumEither) => DatumEither {
+ return match(
+ // Cast to any as TS does not infer the B in B | J
+ // deno-lint-ignore no-explicit-any
+ constInitial,
+ constPending,
+ failure,
+ (a, refresh) => refresh ? D.toLoading(fati(a)) : fati(a),
+ );
+}
+
+/**
+ * Chain DatumEither based failures, *Left* sides, useful for recovering
+ * from error conditions.
+ *
+ * @since 2.1.0
+ */
+export function recover(
+ fbtj: (b: B) => DatumEither,
+): (ta: DatumEither) => DatumEither {
+ return match(
+ // Cast to any as TS does not infer the A in A | I
+ // deno-lint-ignore no-explicit-any
+ constInitial,
+ constPending,
+ (b, refresh) => refresh ? D.toLoading(fbtj(b)) : fbtj(b),
+ success,
+ );
+}
+
+/**
+ * Provide an alternative for a failed computation.
+ * Useful for implementing defaults.
+ *
+ * @since 2.1.0
+ */
+export function alt(
+ ui: DatumEither,
+): (ta: DatumEither) => DatumEither {
+ return (ua) => isFailure(ua) ? ui : ua;
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getShowableDatumEither(
+ CA: Showable,
+ CB: Showable,
+): Showable> {
+ return D.getShowableDatum(E.getShowableEither(CB, CA));
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getCombinableDatumEither(
+ CA: Combinable,
+ CB: Combinable,
+): Combinable> {
+ return D.getCombinableDatum(E.getCombinableEither(CA, CB));
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getInitializableDatumEither(
+ CA: Initializable,
+ CB: Initializable,
+): Initializable> {
+ return D.getInitializableDatum(E.getInitializableEither(CA, CB));
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getComparableDatumEither(
+ CA: Comparable,
+ CB: Comparable,
+): Comparable> {
+ return D.getComparableDatum(E.getComparableEither(CB, CA));
+}
+
+/**
+ * @since 2.1.0
+ */
+export function getSortableDatumEither(
+ CA: Sortable,
+ CB: Sortable,
+): Sortable> {
+ return D.getSortableDatum(E.getSortableEither(CB, CA));
+}
+
+/**
+ * @since 2.1.0
+ */
+export const ApplicableDatumEither: Applicable = {
+ apply,
+ map,
+ wrap,
+};
+
+/**
+ * @since 2.1.0
+ */
+export const BimappableDatumEither: Bimappable = {
+ map,
+ mapSecond,
+};
+
+/**
+ * @since 2.1.0
+ */
+export const FlatmappableDatumEither: Flatmappable = {
+ wrap,
+ apply,
+ map,
+ flatmap,
+};
+
+/**
+ * @since 2.1.0
+ */
+export const FailableDatumEither: Failable = {
+ wrap,
+ apply,
+ map,
+ flatmap,
+ alt,
+ fail,
+ recover,
+};
+
+/**
+ * @since 2.1.0
+ */
+export const MappableDatumEither: Mappable = {
+ map,
+};
+
+/**
+ * @since 2.1.0
+ */
+export const WrappableDatumEither: Wrappable = {
+ wrap,
+};
+
+/**
+ * @since 2.1.0
+ */
+export const tap: Tap = createTap(FailableDatumEither);
+
+/**
+ * @since 2.1.0
+ */
+export const bind: Bind = createBind(
+ FlatmappableDatumEither,
+);
+
+/**
+ * @since 2.1.0
+ */
+export const bindTo: BindTo = createBindTo(
+ FlatmappableDatumEither,
+);
diff --git a/deno.json b/deno.json
index db6dcd4..a38ead1 100644
--- a/deno.json
+++ b/deno.json
@@ -1,6 +1,6 @@
{
"name": "@baetheus/fun",
- "version": "2.0.2",
+ "version": "2.1.0",
"exports": {
"./applicable": "./applicable.ts",
"./array": "./array.ts",
@@ -13,6 +13,7 @@
"./comparable": "./comparable.ts",
"./composable": "./composable.ts",
"./datum": "./datum.ts",
+ "./datum_either": "./datum_either.ts",
"./decoder": "./decoder.ts",
"./either": "./either.ts",
"./failable": "./failable.ts",
@@ -51,6 +52,7 @@
"./traversable": "./traversable.ts",
"./tree": "./tree.ts",
"./wrappable": "./wrappable.ts",
+ "./contrib/dux": "./contrib/dux.ts",
"./contrib/fast-check": "./contrib/fast-check.ts",
"./contrib/free": "./contrib/free.ts",
"./contrib/most": "./contrib/most.ts"
diff --git a/optic.ts b/optic.ts
index 3b54001..8e13646 100644
--- a/optic.ts
+++ b/optic.ts
@@ -30,6 +30,7 @@
import type { $, Kind } from "./kind.ts";
import type { Comparable } from "./comparable.ts";
+import type { DatumEither } from "./datum_either.ts";
import type { Initializable } from "./initializable.ts";
import type { Either } from "./either.ts";
import type { Flatmappable } from "./flatmappable.ts";
@@ -41,13 +42,14 @@ import type { Refinement } from "./refinement.ts";
import type { Traversable } from "./traversable.ts";
import type { Tree } from "./tree.ts";
-import * as I from "./identity.ts";
-import * as O from "./option.ts";
import * as A from "./array.ts";
-import * as R from "./record.ts";
+import * as DE from "./datum_either.ts";
import * as E from "./either.ts";
+import * as I from "./identity.ts";
import * as M from "./map.ts";
+import * as O from "./option.ts";
import * as P from "./pair.ts";
+import * as R from "./record.ts";
import { TraversableSet } from "./set.ts";
import { TraversableTree } from "./tree.ts";
import { isNotNil } from "./nil.ts";
@@ -1520,3 +1522,21 @@ export const first: (
export const second: (
optic: Optic>,
) => Optic, S, B> = compose(lens(P.getSecond, P.mapSecond));
+
+/**
+ * @since 2.1.0
+ */
+export const success: (
+ optic: Optic>,
+) => Optic, S, A> = compose(
+ prism(DE.getSuccess, DE.success, DE.map),
+);
+
+/**
+ * @since 2.1.0
+ */
+export const failure: (
+ optic: Optic>,
+) => Optic, S, B> = compose(
+ prism(DE.getFailure, DE.failure, DE.mapSecond),
+);