diff --git a/.fslckout b/.fslckout deleted file mode 100644 index 181ea9d..0000000 Binary files a/.fslckout and /dev/null differ diff --git a/README.md b/README.md index 4d9407c..3ab131b 100644 --- a/README.md +++ b/README.md @@ -43,29 +43,35 @@ structures and more general algebraic structures. | Type | Algebraic Data Type | Algebraic Structure | Native | Other Names | | ------------------------------------ | ------------------- | ------------------- | ------ | ------------------------------------- | | [Applicable](./applicable.ts) | | ✓ | | Applicative | -| [ReadonlyArray](./array.ts) | ✓ | | ✓ | Array | -| [Async](./async.ts) | ✓ | | | Task | -| [AsyncEither](./async_either.ts) | ✓ | | | TaskEither | -| [AsyncIterable](./async_iterable.ts) | ✓ | | ✓ | | | [Bimappable](./bimappable.ts) | | ✓ | | Bifunctor, Covariant Bifunctor | -| [Boolean](./boolean.ts) | ✓ | | ✓ | | | [Combinable](./combinable.ts) | | ✓ | | Semigroup | | [Comparable](./comparable.ts) | | ✓ | | Setoid, Eq | | [Composable](./composable.ts) | | ✓ | | Category | -| [Decoder](./decoder.ts) | ✓ | | | | -| [Either](./either.ts) | ✓ | | | | | [Failable](./failable.ts) | | ✓ | | Validation | | [Filterable](./filterable.ts) | | ✓ | | | | [Flatmappable](./flatmappable.ts) | | ✓ | | Monad | +| [Foldable](./foldable.ts) | | ✓ | | Reducible | +| [Initializable](./initializable.ts) | | ✓ | | Monoid | +| [Mappable](./mappable.ts) | | ✓ | | Functor, Covariant Functor | +| [Premappable](./premappable.ts) | | ✓ | | Contravariant, Contravariant Functor | +| [Schemable](./schemable.ts) | | ✓ | | | +| [Showable](./showable.ts) | | ✓ | | Show | +| [Sortable](./sortable.ts) | | ✓ | | Ord | +| [Traversable](./traversable.ts) | | ✓ | | | +| [Wrappable](./wrappable.ts) | | ✓ | | Pointed | +| [ReadonlyArray](./array.ts) | ✓ | | ✓ | Array | +| [Async](./async.ts) | ✓ | | | Task | +| [AsyncEither](./async_either.ts) | ✓ | | | TaskEither | +| [AsyncIterable](./async_iterable.ts) | ✓ | | ✓ | | +| [Boolean](./boolean.ts) | ✓ | | ✓ | | +| [Decoder](./decoder.ts) | ✓ | | | | +| [Either](./either.ts) | ✓ | | | | | [Fn](./fn.ts) | ✓ | | ✓ | Reader | | [FnEither](./fn_either.ts) | ✓ | | | ReaderEither | -| [Free](./free.ts) | ✓ | | | FreeSemigroup | | [Identity](./identity.ts) | ✓ | | | Trivial | -| [Initializable](./initializable.ts) | | ✓ | | Monoid | | [Iterable](./iterable.ts) | ✓ | | ✓ | | | [JsonSchema](./json_schema.ts) | ✓ | | | | | [ReadonlyMap](./map.ts) | ✓ | | ✓ | Map | -| [Mappable](./mappable.ts) | | ✓ | | Functor, Covariant Functor | | [Newtype](./newtype.ts) | | | | Brand, Branded Type | | [Nilable](./nilable.ts) | ✓ | | | | | [Number](./number.ts) | ✓ | | ✓ | | @@ -73,22 +79,15 @@ structures and more general algebraic structures. | [Option](./option.ts) | ✓ | | | Maybe | | [Pair](./pair.ts) | ✓ | | | Separated | | [Predicate](./predicate.ts) | ✓ | | | | -| [Premappable](./premappable.ts) | | ✓ | | Contravariant, Contravariant Functor | | [Promise](./promise.ts) | ✓ | | ✓ | | -| [Reducible](./reducible.ts) | | ✓ | | Foldable | | [Refinement](./refinement.ts) | ✓ | | | | -| [Schemable](./schemable.ts) | | ✓ | | | | [ReadonlySet](./set.ts) | ✓ | | ✓ | Set | -| [Showable](./showable.ts) | | ✓ | | Show | -| [Sortable](./sortable.ts) | | ✓ | | Ord | | [State](./state.ts) | ✓ | | | | | [String](./string.ts) | ✓ | | ✓ | | | [Sync](./sync.ts) | ✓ | | | IO | | [SyncEither](./sync_either.ts) | ✓ | | | IOEither | | [These](./these.ts) | ✓ | | | | -| [Traversable](./traversable.ts) | | ✓ | | | | [Tree](./tree.ts) | ✓ | | | | -| [Wrappable](./wrappable.ts) | | ✓ | | Pointed | ## Major Versions diff --git a/async_either.ts b/async_either.ts index 770bc98..8c74d1b 100644 --- a/async_either.ts +++ b/async_either.ts @@ -10,15 +10,21 @@ import type { Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; +import type { Async } from "./async.ts"; import type { Bimappable } from "./bimappable.ts"; import type { Combinable } from "./combinable.ts"; import type { Either } from "./either.ts"; +import type { Failable } from "./failable.ts"; import type { Flatmappable } from "./flatmappable.ts"; -import type { Async } from "./async.ts"; +import type { Initializable } from "./initializable.ts"; +import type { Mappable } from "./mappable.ts"; +import type { Wrappable } from "./wrappable.ts"; import * as E from "./either.ts"; import * as A from "./async.ts"; import * as P from "./promise.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; import { handleThrow, pipe } from "./fn.ts"; import { resolve } from "./promise.ts"; @@ -369,13 +375,30 @@ export function match( /** * @since 2.0.0 */ -export function getCombinableAsyncEither(CA: Combinable, CB: Combinable): Combinable> { - const combinableEither = E.getRightInitializable - +export function getCombinableAsyncEither( + CA: Combinable, + CB: Combinable, +): Combinable> { + const { combine } = E.getCombinableEither(CA, CB); return { - combine: second => first => async () => + combine: (second) => (first) => async () => + combine(await second())(await first()), + }; +} - } +/** + * @since 2.0.0 + */ +export function getInitializableAsyncEither( + CA: Initializable, + CB: Initializable, +): Initializable> { + const { init, combine } = E.getInitializableEither(CA, CB); + return { + init: () => () => resolve(init()), + combine: (second) => (first) => async () => + combine(await second())(await first()), + }; } /** @@ -415,3 +438,58 @@ export const FlatmappableAsyncEitherSequential: Flatmappable = map, flatmap, }; + +/** + * @since 2.0.0 + */ +export const FailableAsyncEitherParallel: Failable = { + wrap, + apply, + map, + flatmap, + alt, + fail, + recover, +}; + +/** + * @since 2.0.0 + */ +export const FailableAsyncEitherSequential: Failable = { + wrap, + apply: applySequential, + map, + flatmap, + alt, + fail, + recover, +}; + +/** + * @since 2.0.0 + */ +export const MappableAsyncEither: Mappable = { + map, +}; + +/** + * @since 2.0.0 + */ +export const WrappableAsyncEither: Wrappable = { + wrap, +}; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableAsyncEitherParallel); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableAsyncEitherParallel); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(FlatmappableAsyncEitherParallel); diff --git a/async_iterable.ts b/async_iterable.ts index fa93f77..b42fc3d 100644 --- a/async_iterable.ts +++ b/async_iterable.ts @@ -1,26 +1,48 @@ +/** + * This file contains the AsyncIterable algebraic data type. AsyncIterable is + * the generic type for javascripts built in AsyncIterator type. + * + * @module AsyncIterable + * @since 2.0.0 + */ + +import type { Kind, Out } from "./kind.ts"; +import type { Applicable } from "./applicable.ts"; +import type { Wrappable } from "./wrappable.ts"; import type { Filterable } from "./filterable.ts"; +import type { Mappable } from "./mappable.ts"; import type { Option } from "./option.ts"; -import type { Kind, Out } from "./kind.ts"; import type { Flatmappable } from "./flatmappable.ts"; import type { Either } from "./either.ts"; import type { Pair } from "./pair.ts"; import type { Predicate } from "./predicate.ts"; import type { Refinement } from "./refinement.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; import { isLeft, isRight } from "./either.ts"; import { isSome } from "./option.ts"; import { wait } from "./promise.ts"; +/** + * @since 2.0.0 + */ export interface KindAsyncIterable extends Kind { readonly kind: AsyncIterable>; } +/** + * @since 2.0.0 + */ export function asyncIterable( fa: () => AsyncIterator, ): AsyncIterable { return { [Symbol.asyncIterator]: fa }; } +/** + * @since 2.0.0 + */ export function fromIterable(ta: Iterable): AsyncIterable { return asyncIterable(async function* () { for (const a of ta) { @@ -29,6 +51,9 @@ export function fromIterable(ta: Iterable): AsyncIterable { }); } +/** + * @since 2.0.0 + */ export function range( count: number = Number.POSITIVE_INFINITY, start = 0, @@ -45,6 +70,9 @@ export function range( }); } +/** + * @since 2.0.0 + */ export function clone(ta: AsyncIterable): AsyncIterable { const cache: Promise>[] = []; let iterator: AsyncIterator; @@ -70,12 +98,18 @@ export function clone(ta: AsyncIterable): AsyncIterable { }); } +/** + * @since 2.0.0 + */ export function wrap(a: A): AsyncIterable { return asyncIterable(async function* () { yield a; }); } +/** + * @since 2.0.0 + */ export function apply( ua: AsyncIterable, ): (ufai: AsyncIterable<(a: A) => I>) => AsyncIterable { @@ -89,6 +123,9 @@ export function apply( }); } +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I | PromiseLike, ): (ta: AsyncIterable) => AsyncIterable { @@ -100,6 +137,9 @@ export function map( }); } +/** + * @since 2.0.0 + */ export function flatmap( fati: (a: A) => AsyncIterable, ): (ta: AsyncIterable) => AsyncIterable { @@ -113,6 +153,9 @@ export function flatmap( }); } +/** + * @since 2.0.0 + */ export function forEach( onValue: (a: A) => unknown | PromiseLike, onDone: () => unknown | PromiseLike = () => {}, @@ -125,12 +168,18 @@ export function forEach( }; } +/** + * @since 2.0.0 + */ export function delay( ms: number, ): (ta: AsyncIterable) => AsyncIterable { return map((a) => wait(ms).then(() => a)); } +/** + * @since 2.0.0 + */ export function filter( refinement: Refinement, ): (ua: AsyncIterable) => AsyncIterable; @@ -150,6 +199,9 @@ export function filter( }); } +/** + * @since 2.0.0 + */ export function filterMap( predicate: (a: A) => Option, ): (ua: AsyncIterable) => AsyncIterable { @@ -166,6 +218,9 @@ export function filterMap( ); } +/** + * @since 2.0.0 + */ export function partition( refinement: (a: A) => a is B, ): (ua: AsyncIterable) => Pair, AsyncIterable>; @@ -196,6 +251,9 @@ export function partition( }; } +/** + * @since 2.0.0 + */ export function partitionMap( predicate: (a: A) => Either, ): (ua: AsyncIterable) => Pair, AsyncIterable> { @@ -222,6 +280,9 @@ export function partitionMap( }; } +/** + * @since 2.0.0 + */ export function fold( foldr: (value: O, accumulator: A, index: number) => O, initial: O, @@ -236,6 +297,9 @@ export function fold( }; } +/** + * @since 2.0.0 + */ export async function collect( ta: AsyncIterable, ): Promise> { @@ -246,6 +310,9 @@ export async function collect( return result; } +/** + * @since 2.0.0 + */ export function takeUntil( predicate: Predicate, ): (ta: AsyncIterable) => AsyncIterable { @@ -260,12 +327,18 @@ export function takeUntil( }); } +/** + * @since 2.0.0 + */ export function takeWhile( predicate: Predicate, ): (ta: AsyncIterable) => AsyncIterable { return takeUntil((a) => !predicate(a)); } +/** + * @since 2.0.0 + */ export function scan( foldr: (accumulator: O, value: A, index: number) => O, initial: O, @@ -281,18 +354,9 @@ export function scan( }); } -export function tap( - fa: (a: A) => void, -): (ta: AsyncIterable) => AsyncIterable { - return (ta) => - asyncIterable(async function* () { - for await (const a of ta) { - fa(a); - yield a; - } - }); -} - +/** + * @since 2.0.0 + */ export function repeat( n: number, ): (ta: AsyncIterable) => AsyncIterable { @@ -307,6 +371,9 @@ export function repeat( }); } +/** + * @since 2.0.0 + */ export function take(n: number): (ta: AsyncIterable) => AsyncIterable { return (ta: AsyncIterable) => asyncIterable(async function* () { @@ -320,6 +387,23 @@ export function take(n: number): (ta: AsyncIterable) => AsyncIterable { }); } +/** + * @since 2.0.0 + */ +export const ApplicableAsyncIterable: Applicable = { + apply, + map, + wrap, +}; + +/** + * @since 2.0.0 + */ +export const MappableAsyncIterable: Mappable = { map }; + +/** + * @since 2.0.0 + */ export const FlatmappableAsyncIterable: Flatmappable = { apply, flatmap, @@ -327,9 +411,32 @@ export const FlatmappableAsyncIterable: Flatmappable = { wrap, }; +/** + * @since 2.0.0 + */ export const FilterableAsyncIterable: Filterable = { filter, filterMap, partition, partitionMap, }; + +/** + * @since 2.0.0 + */ +export const WrappableAsyncIterable: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableAsyncIterable); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableAsyncIterable); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableAsyncIterable); diff --git a/boolean.ts b/boolean.ts index a6a7a98..f858013 100644 --- a/boolean.ts +++ b/boolean.ts @@ -6,6 +6,7 @@ */ import type { Ordering, Sortable } from "./sortable.ts"; +import type { Combinable } from "./combinable.ts"; import type { Initializable } from "./initializable.ts"; import type { Comparable } from "./comparable.ts"; import type { Showable } from "./showable.ts"; @@ -182,6 +183,28 @@ export const SortableBoolean: Sortable = fromSort(sort); */ export const ComparableBoolean: Comparable = { compare }; +/** + * The canonical implementation of Combinable for boolean that + * combines using the logical and operator. It contains the + * method combine. + * + * @since 2.0.0 + */ +export const CombinableBooleanAll: Combinable = { + combine: and, +}; + +/** + * The canonical implementation of Combinable for boolean that + * combines using the logical or operator. It contains the + * method combine. + * + * @since 2.0.0 + */ +export const CombinableBooleanAny: Combinable = { + combine: or, +}; + /** * The canonical implementation of Initializable for boolean that * combines using the logical and operator. It contains the @@ -191,7 +214,7 @@ export const ComparableBoolean: Comparable = { compare }; */ export const InitializableBooleanAll: Initializable = { combine: and, - init: constFalse, + init: constTrue, }; /** @@ -203,7 +226,7 @@ export const InitializableBooleanAll: Initializable = { */ export const InitializableBooleanAny: Initializable = { combine: or, - init: constTrue, + init: constFalse, }; /** diff --git a/combinable.ts b/combinable.ts index a2c83a2..7d134f5 100644 --- a/combinable.ts +++ b/combinable.ts @@ -8,11 +8,11 @@ */ import type { Sortable } from "./sortable.ts"; -import type { ReadonlyRecord } from "./record.ts"; -import type { NonEmptyArray } from "./array.ts"; import * as S from "./sortable.ts"; -import { pipe, uncurry2 } from "./fn.ts"; + +type ReadonlyRecord = Readonly>; +type NonEmptyArray = readonly [A, ...A[]]; /** * The Combine function in a Combinable. @@ -329,9 +329,7 @@ export function min(sortable: Sortable): Combinable { */ export function intercalcate(middle: A) { return ({ combine }: Combinable): Combinable => - fromCombine((second) => (first) => - pipe(first, combine(middle), combine(second)) - ); + fromCombine((second) => (first) => combine(second)(combine(middle)(first))); } /** @@ -382,6 +380,6 @@ export function constant(a: A): Combinable { export function getCombineAll( { combine }: Combinable, ): (...as: NonEmptyArray) => A { - const _combine = uncurry2(combine); + const _combine = (first: A, second: A) => combine(second)(first); return (...as) => as.reduce(_combine); } diff --git a/comparable.ts b/comparable.ts index e432fb6..8381def 100644 --- a/comparable.ts +++ b/comparable.ts @@ -9,7 +9,6 @@ */ import type { Hold, In, Kind, Out, Spread } from "./kind.ts"; -import type { Premappable } from "./premappable.ts"; import type { NonEmptyArray } from "./array.ts"; import type { ReadonlyRecord } from "./record.ts"; import type { Literal, Schemable } from "./schemable.ts"; @@ -466,7 +465,11 @@ export function union( second: Comparable, ): (first: Comparable) => Comparable { return (first: Comparable) => { - const _first = handleThrow(uncurry2(first.compare), identity, () => false); + const _first = handleThrow( + uncurry2(first.compare), + identity, + () => false, + ); const _second = handleThrow( uncurry2(second.compare), identity, @@ -619,15 +622,6 @@ export function premap( fromCompare((second) => (first) => compare(fld(second))(fld(first))); } -/** - * The canonical implementation of Premappable for Comparable. - * - * @since 2.0.0 - */ -export const PremappableComparable: Premappable = { - premap, -}; - /** * The canonical implementation of Schemable for a Comparable. It contains * the methods unknown, string, number, boolean, literal, nullable, diff --git a/composable.ts b/composable.ts index 80feba1..e32b18d 100644 --- a/composable.ts +++ b/composable.ts @@ -23,5 +23,5 @@ export interface Composable extends Hold { second: $, ) => ( first: $, - ) => $; + ) => $; } diff --git a/free.ts b/contrib/free.ts similarity index 72% rename from free.ts rename to contrib/free.ts index 6a116fc..98d6cd7 100644 --- a/free.ts +++ b/contrib/free.ts @@ -1,31 +1,58 @@ -import type { Kind, Out } from "./kind.ts"; -import type { Combinable } from "./combinable.ts"; - -import {} from "./array.ts"; -import {} from "./record.ts"; -import { flow, pipe } from "./fn.ts"; - +/** + * This file contains the Free algebraic data type. Free is a data type that is + * used primarily to create a Combinable for any given data structure. It is + * useful when one wants to use combine things without deciding on a specific + * data structure to implement. + * + * @experimental + * @module Free + * @since 2.0.0 + */ + +import type { Kind, Out } from "../kind.ts"; +import type { Combinable } from "../combinable.ts"; + +import { flow, pipe } from "../fn.ts"; + +/** + * @since 2.0.0 + */ export type Node = { readonly tag: "Node"; readonly value: A; }; +/** + * @since 2.0.0 + */ export type Link = { readonly tag: "Link"; readonly first: Free; readonly second: Free; }; +/** + * @since 2.0.0 + */ export type Free = Node | Link; +/** + * @since 2.0.0 + */ export interface KindFree extends Kind { readonly kind: Free>; } +/** + * @since 2.0.0 + */ export function node(value: A): Free { return { tag: "Node", value }; } +/** + * @since 2.0.0 + */ export function link( first: Free, second: Free, @@ -33,14 +60,23 @@ export function link( return { tag: "Link", first, second }; } +/** + * @since 2.0.0 + */ export function isNode(ua: Free): ua is Node { return ua.tag === "Node"; } +/** + * @since 2.0.0 + */ export function isLink(ua: Free): ua is Link { return ua.tag === "Link"; } +/** + * @since 2.0.0 + */ export function match( onNode: (value: A) => O, onLink: (first: Free, second: Free) => O, @@ -55,16 +91,25 @@ export function match( }; } +/** + * @since 2.0.0 + */ export function combine( second: Free, ): (first: Free) => Free { return (first) => ({ tag: "Link", first, second }); } +/** + * @since 2.0.0 + */ export function wrap(a: A): Free { return node(a); } +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I, ): (ua: Free) => Free { @@ -75,6 +120,9 @@ export function map( return go; } +/** + * @since 2.0.0 + */ export function flatmap( faui: (a: A) => Free, ): (ua: Free) => Free { @@ -85,10 +133,16 @@ export function flatmap( return go; } +/** + * @since 2.0.0 + */ export function apply(ua: Free): (ufai: Free<(a: A) => I>) => Free { return (ufai) => pipe(ufai, flatmap(flow(map, (fn) => fn(ua)))); } +/** + * @since 2.0.0 + */ export function fold( foldr: (value: A, accumulator: O) => O, initial: O, @@ -108,8 +162,12 @@ export function fold( return go; } +/** + * @since 2.0.0 + */ export function getCombinable(): Combinable> { return { combine }; } // TODO: Showable, Sortable, Traversable +// diff --git a/datum.ts b/datum.ts index 156e1fa..f45cad3 100644 --- a/datum.ts +++ b/datum.ts @@ -1,82 +1,154 @@ +/** + * 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 { Filterable } from "./filterable.ts"; +import type { Mappable } from "./mappable.ts"; +import type { Wrappable } from "./wrappable.ts"; +import type { Comparable } from "./comparable.ts"; +import type { Either } from "./either.ts"; import type { Flatmappable } from "./flatmappable.ts"; -import type { Sortable } from "./sortable.ts"; import type { Initializable } from "./initializable.ts"; -import type { Comparable } from "./comparable.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 * as O from "./option.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; import { fromSort } from "./sortable.ts"; -import { isNotNil } from "./nilable.ts"; -import { flow, identity, pipe } from "./fn.ts"; +import { isNotNil } from "./nil.ts"; +import { flow, handleThrow, identity, pipe } from "./fn.ts"; /** - * TODO: Lets get a monoid in here for tracking progress. + * @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; } -export function tryCatch(fa: () => A): Datum { - try { - return replete(fa()); - } catch (_) { - return 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, @@ -89,34 +161,58 @@ export function toLoading(ta: Datum): Datum { ); } +/** + * @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, @@ -137,14 +233,23 @@ export function match( }; } +/** + * @since 2.0.0 + */ export function getOrElse(onNone: () => 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, @@ -154,6 +259,9 @@ export function map(fai: (a: A) => I): (ta: Datum) => Datum { ); } +/** + * @since 2.0.0 + */ export function apply( ua: Datum, ): (ufai: Datum<(a: A) => I>) => Datum { @@ -176,6 +284,9 @@ export function apply( } } +/** + * @since 2.0.0 + */ export function flatmap( fati: (a: A) => Datum, ): (ta: Datum) => Datum { @@ -187,10 +298,16 @@ export function flatmap( ); } +/** + * @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, @@ -198,6 +315,105 @@ export function fold( 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, ): ( @@ -212,6 +428,9 @@ export function traverse( ); } +/** + * @since 2.0.0 + */ export function getShowableDatum({ show }: Showable): Showable> { return ({ show: match( @@ -223,26 +442,44 @@ export function getShowableDatum({ show }: Showable): Showable> { }); } -export function getInitializableDatum( - S: Initializable, -): Initializable> { +/** + * @since 2.0.0 + */ +export function getCombinableDatum( + S: Combinable, +): Combinable> { return ({ - combine: (mx) => + combine: (second) => match( - () => mx, - () => toLoading(mx), + () => second, + () => toLoading(second), (v) => - isSome(mx) - ? (isRefresh(mx) - ? refresh(S.combine(mx.value)(v)) - : replete(S.combine(mx.value)(v))) - : (isPending(mx) ? refresh(v) : replete(v)), - (v) => isSome(mx) ? refresh(S.combine(mx.value)(v)) : refresh(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) => @@ -255,6 +492,9 @@ export function getComparableDatum(S: Comparable): Comparable> { }); } +/** + * @since 2.0.0 + */ export function getSortableDatum(O: Sortable): Sortable> { return fromSort((fst, snd) => pipe( @@ -270,6 +510,25 @@ export function getSortableDatum(O: Sortable): Sortable> { ); } +/** + * @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, @@ -277,8 +536,43 @@ export const FlatmappableDatum: Flatmappable = { 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 = createTap(FlatmappableDatum); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableDatum); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableDatum); diff --git a/decoder.ts b/decoder.ts index 7f2ca9a..cf88657 100644 --- a/decoder.ts +++ b/decoder.ts @@ -7,7 +7,6 @@ * @todo Revisit array, tuple, record, and struct to have a concept of ...rest * * @module Decoder - * * @since 2.0.0 */ @@ -15,6 +14,7 @@ import type { In, Kind, Out, Spread } from "./kind.ts"; import type { AnyArray, NonEmptyArray } from "./array.ts"; import type { Applicable } from "./applicable.ts"; import type { Combinable } from "./combinable.ts"; +import type { Composable } from "./composable.ts"; import type { Either } from "./either.ts"; import type { Flatmappable } from "./flatmappable.ts"; import type { FnEither } from "./fn_either.ts"; @@ -22,8 +22,10 @@ import type { Forest } from "./tree.ts"; import type { Literal, Schemable } from "./schemable.ts"; import type { Mappable } from "./mappable.ts"; import type { Predicate } from "./predicate.ts"; +import type { Premappable } from "./premappable.ts"; import type { ReadonlyRecord } from "./record.ts"; import type { Refinement } from "./refinement.ts"; +import type { Wrappable } from "./wrappable.ts"; import * as FE from "./fn_either.ts"; import * as TR from "./tree.ts"; @@ -33,6 +35,8 @@ import * as R from "./record.ts"; import * as O from "./option.ts"; import * as S from "./schemable.ts"; import * as G from "./refinement.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; import { fromCombine } from "./combinable.ts"; import { flow, memoize, pipe } from "./fn.ts"; @@ -806,9 +810,7 @@ export function unwrap(ta: Decoded): Either { * @since 2.0.0 */ export const FlatmappableDecoded: Flatmappable = E - .getRightFlatmappable( - CombinableDecodeError, - ); + .getFlatmappableRight(CombinableDecodeError); /** * A traversal over a Record of Decoded results. @@ -1830,20 +1832,25 @@ export interface KindUnknownDecoder extends Kind { } /** - * The canonical implementation of Mappable for Decoder. It contains - * the method map. + * The canonical implementation of Applicable for Decoder. It contains + * the methods of, ap, and map. * * @since 2.0.0 */ -export const MappableDecoder: Mappable = { map }; +export const ApplicableDecoder: Applicable = { wrap, apply, map }; /** - * The canonical implementation of Applicable for Decoder. It contains - * the methods of, ap, and map. + * @since 2.0.0 + */ +export const ComposableDecoder: Composable = { compose, id }; + +/** + * The canonical implementation of Mappable for Decoder. It contains + * the method map. * * @since 2.0.0 */ -export const ApplicableDecoder: Applicable = { wrap, apply, map }; +export const MappableDecoder: Mappable = { map }; /** * The canonical implementation of Flatmappable for Decoder. It contains @@ -1856,6 +1863,15 @@ export const FlatmappableDecoder: Flatmappable = FE CombinableDecodeError, ); +/** + * @since 2.0.0 + */ +export const PremappableDecoder: Premappable = { + premap, + map, + dimap, +}; + /** * The canonical implementation of Schemable for Decoder. It contains * the methods unknown, string, number, boolean, literal, nullable, undefinable, @@ -1880,3 +1896,23 @@ export const SchemableDecoder: Schemable = { union, lazy, }; + +/** + * @since 2.0.0 + */ +export const WrappableDecoder: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableDecoder); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableDecoder); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableDecoder); diff --git a/either.ts b/either.ts index b7bc153..18bff08 100644 --- a/either.ts +++ b/either.ts @@ -1,14 +1,26 @@ +/** + * This file contains the Either algebraic data type. Either is used to + * represent two exclusive types. Generally, Either is used to represent either + * a successful computation or a failed computation, with the result of the + * failed computation being kept in Left. + * + * @module Either + * @since 2.0.0 + */ + import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; -import type { Initializable } from "./initializable.ts"; import type { Bimappable } from "./bimappable.ts"; import type { Combinable } from "./combinable.ts"; import type { Comparable } from "./comparable.ts"; import type { Failable } from "./failable.ts"; +import type { Filterable } from "./filterable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; +import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; +import type { Pair } from "./pair.ts"; import type { Predicate } from "./predicate.ts"; -import type { Foldable } from "./foldable.ts"; import type { Refinement } from "./refinement.ts"; import type { Showable } from "./showable.ts"; import type { Sortable } from "./sortable.ts"; @@ -16,59 +28,83 @@ import type { Traversable } from "./traversable.ts"; import type { Wrappable } from "./wrappable.ts"; import * as O from "./option.ts"; -import { isNotNil } from "./nilable.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; +import { isNotNil } from "./nil.ts"; import { fromCompare } from "./comparable.ts"; import { fromSort } from "./sortable.ts"; import { flow, pipe } from "./fn.ts"; +/** + * @since 2.0.0 + */ export type Left = { tag: "Left"; left: L }; +/** + * @since 2.0.0 + */ export type Right = { tag: "Right"; right: R }; +/** + * @since 2.0.0 + */ export type Either = Left | Right; +/** + * @since 2.0.0 + */ export interface KindEither extends Kind { readonly kind: Either, Out>; } +/** + * @since 2.0.0 + */ export interface KindRightEither extends Kind { readonly kind: Either>; } +/** + * @since 2.0.0 + */ export function left(left: E): Either { return ({ tag: "Left", left }); } +/** + * @since 2.0.0 + */ export function right(right: A): Either { return ({ tag: "Right", right }); } +/** + * @since 2.0.0 + */ export function wrap(a: A): Either { return right(a); } +/** + * @since 2.0.0 + */ export function fail(b: B): Either { return left(b); } +/** + * @since 2.0.0 + */ export function fromNullable(fe: () => E) { return ( a: A, ): Either> => (isNotNil(a) ? right(a) : left(fe())); } -export function tryCatch( - fa: () => A, - onError: (e: unknown) => E, -): Either { - try { - return right(fa()); - } catch (e) { - return left(onError(e)); - } -} - -export function tryCatchWrap( +/** + * @since 2.0.0 + */ +export function tryCatch( fn: (...as: AS) => A, onError: (e: unknown) => E, ): (...as: AS) => Either { @@ -81,6 +117,9 @@ export function tryCatchWrap( }; } +/** + * @since 2.0.0 + */ export function fromPredicate( refinement: Refinement, ): (a: A) => Either; @@ -93,6 +132,9 @@ export function fromPredicate( return (a: A) => predicate(a) ? right(a) : left(a); } +/** + * @since 2.0.0 + */ export function match( onLeft: (left: L) => B, onRight: (right: R) => B, @@ -100,112 +142,44 @@ export function match( return (ta) => isLeft(ta) ? onLeft(ta.left) : onRight(ta.right); } +/** + * @since 2.0.0 + */ export function getOrElse(onLeft: (e: E) => A) { return (ma: Either): A => isLeft(ma) ? onLeft(ma.left) : ma.right; } +/** + * @since 2.0.0 + */ export function getRight(ma: Either): O.Option { return pipe(ma, match(O.constNone, O.some)); } +/** + * @since 2.0.0 + */ export function getLeft(ma: Either): O.Option { return pipe(ma, match(O.some, O.constNone)); } +/** + * @since 2.0.0 + */ export function isLeft(m: Either): m is Left { return m.tag === "Left"; } +/** + * @since 2.0.0 + */ export function isRight(m: Either): m is Right { return m.tag === "Right"; } -export function getShowable( - SB: Showable, - SA: Showable, -): Showable> { - return ({ - show: match( - (left) => `Left(${SB.show(left)})`, - (right) => `Right(${SA.show(right)})`, - ), - }); -} - -export function getComparable( - SB: Comparable, - SA: Comparable, -): Comparable> { - return fromCompare((second) => (first) => { - if (isLeft(first)) { - if (isLeft(second)) { - return SB.compare(second.left)(first.left); - } - return false; - } - - if (isLeft(second)) { - return false; - } - return SA.compare(second.right)(first.right); - }); -} - -export function getSortable( - OB: Sortable, - OA: Sortable, -): Sortable> { - return fromSort((fst, snd) => - isLeft(fst) - ? isLeft(snd) ? OB.sort(fst.left, snd.left) : -1 - : isLeft(snd) - ? 1 - : OA.sort(fst.right, snd.right) - ); -} - -export function getLeftInitializable( - { combine, init }: Initializable, -): Initializable> { - return { - init: () => left(init()), - combine: (second) => (first) => - isRight(first) - ? first - : isRight(second) - ? second - : left(combine(second.left)(first.left)), - }; -} - -export function getRightInitializable( - { combine, init }: Initializable, -): Initializable> { - return { - init: () => right(init()), - combine: (second) => (first) => - isLeft(first) - ? first - : isLeft(second) - ? second - : right(combine(second.right)(first.right)), - }; -} - -export function getRightFlatmappable( - { combine }: Combinable, -): Flatmappable> { - return ({ - wrap, - apply: (ua) => (ufai) => - isLeft(ufai) - ? (isLeft(ua) ? left(combine(ua.left)(ufai.left)) : ufai) - : (isLeft(ua) ? ua : right(ufai.right(ua.right))), - map, - flatmap, - }); -} - +/** + * @since 2.0.0 + */ export function bimap( fbj: (b: B) => J, fai: (a: A) => I, @@ -213,29 +187,34 @@ export function bimap( return (ta) => isLeft(ta) ? left(fbj(ta.left)) : right(fai(ta.right)); } +/** + * @since 2.0.0 + */ export function swap(ma: Either): Either { return isLeft(ma) ? right(ma.left) : left(ma.right); } -export function stringifyJSON( - u: unknown, - onError: (reason: unknown) => E, -): Either { - return tryCatch(() => JSON.stringify(u), onError); -} - +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? ta : right(fai(ta.right)); } +/** + * @since 2.0.0 + */ export function mapSecond( fbj: (b: B) => J, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? left(fbj(ta.left)) : ta; } +/** + * @since 2.0.0 + */ export function apply( ua: Either, ): (ufai: Either I>) => Either { @@ -243,12 +222,18 @@ export function apply( isLeft(ua) ? ua : isLeft(ufai) ? ufai : right(ufai.right(ua.right)); } +/** + * @since 2.0.0 + */ export function flatmap( fati: (a: A) => Either, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? ta : fati(ta.right); } +/** + * @since 2.0.0 + */ export function flatmapFirst( faui: (a: A) => Either, ): (ta: Either) => Either { @@ -262,18 +247,27 @@ export function flatmapFirst( }; } +/** + * @since 2.0.0 + */ export function recover( fbui: (b: B) => Either, ): (ua: Either) => Either { return (ua) => isRight(ua) ? ua : fbui(ua.left); } +/** + * @since 2.0.0 + */ export function alt( tb: Either, ): (ta: Either) => Either { return (ta) => isLeft(ta) ? tb : ta; } +/** + * @since 2.0.0 + */ export function fold( foao: (o: O, a: A) => O, o: O, @@ -281,6 +275,9 @@ export function fold( return (ta) => isLeft(ta) ? o : foao(o, ta.right); } +/** + * @since 2.0.0 + */ export function traverse( A: Applicable & Mappable & Wrappable, ): ( @@ -293,10 +290,175 @@ export function traverse( return (faui) => match(onLeft, flow(faui, mapRight)); } +/** + * @since 2.0.0 + */ +export function getShowableEither( + SB: Showable, + SA: Showable, +): Showable> { + return ({ + show: match( + (left) => `Left(${SB.show(left)})`, + (right) => `Right(${SA.show(right)})`, + ), + }); +} + +/** + * @since 2.0.0 + */ +export function getComparableEither( + SB: Comparable, + SA: Comparable, +): Comparable> { + return fromCompare((second) => (first) => { + if (isLeft(first)) { + if (isLeft(second)) { + return SB.compare(second.left)(first.left); + } + return false; + } + + if (isLeft(second)) { + return false; + } + return SA.compare(second.right)(first.right); + }); +} + +/** + * @since 2.0.0 + */ +export function getSortableEither( + OB: Sortable, + OA: Sortable, +): Sortable> { + return fromSort((fst, snd) => + isLeft(fst) + ? isLeft(snd) ? OB.sort(fst.left, snd.left) : -1 + : isLeft(snd) + ? 1 + : OA.sort(fst.right, snd.right) + ); +} + +/** + * @since 2.0.0 + */ +export function getCombinableEither( + CA: Combinable, + CB: Combinable, +): Combinable> { + return { + combine: (second) => (first) => { + if (isLeft(first)) { + if (isLeft(second)) { + return left(CB.combine(second.left)(first.left)); + } + return first; + } else if (isLeft(second)) { + return second; + } + return right(CA.combine(second.right)(first.right)); + }, + }; +} + +/** + * @since 2.0.0 + */ +export function getInitializableEither( + CA: Initializable, + CB: Initializable, +): Initializable> { + return { + init: () => right(CA.init()), + ...getCombinableEither(CA, CB), + }; +} + +/** + * @since 2.0.0 + */ +export function getFlatmappableRight( + { combine }: Combinable, +): Flatmappable> { + return ({ + wrap, + apply: (ua) => (ufai) => + isLeft(ufai) + ? (isLeft(ua) ? left(combine(ua.left)(ufai.left)) : ufai) + : (isLeft(ua) ? ua : right(ufai.right(ua.right))), + map, + flatmap, + }); +} + +/** + * @since 2.0.0 + */ +export function getFilterableEither( + I: Initializable, +): Filterable> { + type Result = Filterable>; + const init = left(I.init()); + return { + filter: ( + (refinement: Refinement) => + (ua: Either): Either => { + if (isLeft(ua)) { + return ua; + } else if (refinement(ua.right)) { + return ua as Right; + } else { + return init; + } + } + ) as Result["filter"], + filterMap: (fai) => (ua) => { + if (isLeft(ua)) { + return ua; + } else { + const oi = fai(ua.right); + return O.isNone(oi) ? init : right(oi.value); + } + }, + partition: ( + (refinement: Refinement) => + (ua: Either): Pair, Either> => { + if (isLeft(ua)) { + return [ua, ua]; + } else if (refinement(ua.right)) { + return [ua as Either, init]; + } else { + return [init, ua]; + } + } + ) as Result["partition"], + partitionMap: (fai) => (ua) => { + if (isLeft(ua)) { + return [ua, ua]; + } + const ei = fai(ua.right); + return isLeft(ei) ? [init, right(ei.left)] : [ei, init]; + }, + }; +} + +/** + * @since 2.0.0 + */ export const ApplicableEither: Applicable = { apply, map, wrap }; +/** + * @since 2.0.0 + */ export const BimappableEither: Bimappable = { map, mapSecond }; +/** + * @since 2.0.0 + */ export const FailableEither: Failable = { alt, apply, @@ -307,6 +469,9 @@ export const FailableEither: Failable = { wrap, }; +/** + * @since 2.0.0 + */ export const FlatmappableEither: Flatmappable = { apply, flatmap, @@ -314,14 +479,41 @@ export const FlatmappableEither: Flatmappable = { wrap, }; +/** + * @since 2.0.0 + */ export const MappableEither: Mappable = { map }; +/** + * @since 2.0.0 + */ export const FoldableEither: Foldable = { fold }; +/** + * @since 2.0.0 + */ export const TraversableEither: Traversable = { map, fold, traverse, }; +/** + * @since 2.0.0 + */ export const WrappableEither: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableEither); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableEither); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableEither); diff --git a/failable.ts b/failable.ts index 6dd27f9..2c2251d 100644 --- a/failable.ts +++ b/failable.ts @@ -27,9 +27,9 @@ export interface Failable extends Flatmappable, Hold { ) => $; readonly recover: ( fbti: (b: B) => $, - ) => ( - ua: $, - ) => $; + ) => ( + ua: $, + ) => $; } /** diff --git a/flatmappable.ts b/flatmappable.ts index 3bc9c85..616ead4 100644 --- a/flatmappable.ts +++ b/flatmappable.ts @@ -10,8 +10,6 @@ import type { $, Hold, Kind } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; -import { pipe } from "./fn.ts"; - /** * A Flatmappable structure. */ @@ -116,12 +114,13 @@ export function createBind({ flatmap, map }: Flatmappable): < export function createFlatmappable( { wrap, flatmap }: Pick, "wrap" | "flatmap">, ): Flatmappable { - const Flatmappable: Flatmappable = { + const result: Flatmappable = { wrap, - apply: (ua) => (ufai) => - pipe(ufai, flatmap((fab) => pipe(ua, Flatmappable.map(fab)))), - map: (fai) => (ta) => pipe(ta, flatmap((a) => wrap(fai(a)))), + apply: ((ua) => flatmap((fai) => result.map(fai)(ua))) as Flatmappable< + U + >["apply"], + map: ((fai) => flatmap((a) => wrap(fai(a)))) as Flatmappable["map"], flatmap, }; - return Flatmappable; + return result; } diff --git a/fn.ts b/fn.ts index 5d1c2b1..fd2d97e 100644 --- a/fn.ts +++ b/fn.ts @@ -1,12 +1,26 @@ +/** + * This file contains the Fn algebraic data type. Fn is short for a unary + * function, which is to say a function that only takes one input variable. + * Most computation can be encoded in Fn, but the standard ADT in most + * functional languages is called Reader. + * + * @module Fn + * @since 2.0.0 + */ import type { In, Kind, Out } from "./kind.ts"; -import type { Premappable } from "./premappable.ts"; -import type { Mappable } from "./mappable.ts"; -import type { Flatmappable } from "./flatmappable.ts"; import type { Applicable } from "./applicable.ts"; +import type { Composable } from "./composable.ts"; +import type { Flatmappable } from "./flatmappable.ts"; +import type { Mappable } from "./mappable.ts"; +import type { Premappable } from "./premappable.ts"; +import type { Wrappable } from "./wrappable.ts"; + +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; /** - * A Fn, also known as Reader or Environment, is a type over a - * javascript function. ie. (a: number, b: string) => string can be + * A Fn, also known as Reader or Environment, is a type over a unary + * javascript function. ie. (a: number) => string can be * a Fn. As an algebraic data type, the associated type class instances * for Fn are limited to single variable inputs so they will look like * (a: number) => string, with only one argument. The purposes of a Fn @@ -179,6 +193,16 @@ export function handleThrow( }; } +/** + * @since 2.0.0 + */ +export function tryCatch( + fda: (...d: D) => A, + onThrow: (e: unknown, d: D) => A, +): (...d: D) => A { + return handleThrow(fda, identity, onThrow); +} + /** * Memoize a unary function using a Map. This * means that this algorithm puposefully leaks memory. @@ -246,6 +270,8 @@ export function todo(): T { * when the type A can be substituted for I and I for A at runtime * without there being any change to the operation of the program. * The primary use case for unsafeCoerce is in Newtype implementations. + * + * @since 2.0.0 */ export function unsafeCoerce(a: A): I { return a as unknown as I; @@ -827,29 +853,18 @@ export function compose( return (first) => flow(first, second); } -// /** -// * The canonical implementation of Profunctor for Fn. It contains -// * the method dimap. -// * -// * @since 2.0.0 -// */ -// export const ProfunctorFn: Profunctor = { dimap }; - /** - * The canonical implementation of Mappable for Fn. It contains - * the method map. + * The canonical implementation of Applicable for Fn. It contains + * the methods of, ap, and map. * * @since 2.0.0 */ -export const MappableFn: Mappable = { map }; +export const ApplicableFn: Applicable = { apply, map, wrap }; /** - * The canonical implementation of Applicable for Fn. It contains - * the methods of, ap, and map. - * * @since 2.0.0 */ -export const ApplicableFn: Applicable = { apply, map, wrap }; +export const ComposableFn: Composable = { compose, id }; /** * The canonical implementation of Flatmappable for Fn. It contains @@ -864,10 +879,38 @@ export const FlatmappableFn: Flatmappable = { wrap, }; +/** + * The canonical implementation of Mappable for Fn. It contains + * the method map. + * + * @since 2.0.0 + */ +export const MappableFn: Mappable = { map }; + /** * The canonical implementation of Premappable for Fn. It contains * the method premap. * * @since 2.0.0 */ -export const PremappableFn: Premappable = { premap }; +export const PremappableFn: Premappable = { premap, map, dimap }; + +/** + * @since 2.0.0 + */ +export const WrappableFn: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableFn); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableFn); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableFn); diff --git a/fn_either.ts b/fn_either.ts index 337b96b..64511b2 100644 --- a/fn_either.ts +++ b/fn_either.ts @@ -8,17 +8,24 @@ */ import type { In, Kind, Out } from "./kind.ts"; +import type { Applicable } from "./applicable.ts"; import type { Bimappable } from "./bimappable.ts"; -import type { Flatmappable } from "./flatmappable.ts"; import type { Combinable } from "./combinable.ts"; -import type { Premappable } from "./premappable.ts"; +import type { Composable } from "./composable.ts"; +import type { Either } from "./either.ts"; +import type { Failable } from "./failable.ts"; +import type { Flatmappable } from "./flatmappable.ts"; +import type { Fn } from "./fn.ts"; +import type { Mappable } from "./mappable.ts"; import type { Predicate } from "./predicate.ts"; +import type { Premappable } from "./premappable.ts"; import type { Refinement } from "./refinement.ts"; -import type { Fn } from "./fn.ts"; -import type { Either } from "./either.ts"; +import type { Wrappable } from "./wrappable.ts"; import * as E from "./either.ts"; import * as F from "./fn.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; /** * A FnEither, also known as ReaderEither, is a type over a variadic @@ -261,6 +268,15 @@ export function wrap( return right(a); } +/** + * @since 2.0.0 + */ +export function fail( + b: B, +): FnEither { + return left(b); +} + /** * Given a FnEither returning a function A => I and a FnEither returning * a value A, combine them into a FnEither returning an I. @@ -468,11 +484,11 @@ export function flatmap( * * @since 2.0.0 */ -export function recover( - fbtj: (b: B) => FnEither, -): ( +export function recover( + fbtj: (b: B) => FnEither, +): ( ua: FnEither, -) => FnEither { +) => FnEither { return (ua) => (d) => { const e = ua(d); return E.isRight(e) ? e : fbtj(e.left)(d); @@ -610,38 +626,6 @@ export function compose( return (first) => F.flow(first, E.flatmap(second)); } -/** - * The canonical implementation of Bimappable for FnEither. It contains - * the methods bimap and mapSecond. - * - * @since 2.0.0 - */ -export const BimappableFnEither: Bimappable = { - map, - mapSecond, -}; - -/** - * The canonical implementation of Flatmappable for FnEither. It contains - * the methods wrap, apply, map, and flatmap. - * - * @since 2.0.0 - */ -export const FlatmappableFnEither: Flatmappable = { - wrap, - apply, - map, - flatmap, -}; - -/** - * The canonical implementation of Premappable for FnEither. It contains - * the method premap. - * - * @since 2.0.0 - */ -export const PremappableFnEither: Premappable = { premap }; - /** * Create a Flatmappable for FnEither where left values are combined using the * supplied Combinable. @@ -678,3 +662,91 @@ export function getRightFlatmappable( flatmap, }); } + +/** + * @since 2.0.0 + */ +export const ApplicableFnEither: Applicable = { + apply, + map, + wrap, +}; + +/** + * The canonical implementation of Bimappable for FnEither. It contains + * the methods bimap and mapSecond. + * + * @since 2.0.0 + */ +export const BimappableFnEither: Bimappable = { + map, + mapSecond, +}; + +/** + * @since 2.0.0 + */ +export const ComposableFnEither: Composable = { compose, id }; + +/** + * The canonical implementation of Flatmappable for FnEither. It contains + * the methods wrap, apply, map, and flatmap. + * + * @since 2.0.0 + */ +export const FlatmappableFnEither: Flatmappable = { + wrap, + apply, + map, + flatmap, +}; + +/** + * @since 2.0.0 + */ +export const FailableFnEither: Failable = { + apply, + flatmap, + map, + wrap, + fail, + alt, + recover, +}; + +/** + * @since 2.0.0 + */ +export const MappableFnEither: Mappable = { map }; + +/** + * The canonical implementation of Premappable for FnEither. It contains + * the method premap. + * + * @since 2.0.0 + */ +export const PremappableFnEither: Premappable = { + premap, + map, + dimap, +}; + +/** + * @since 2.0.0 + */ +export const WrappableFnEither: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableFnEither); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableFnEither); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableFnEither); diff --git a/identity.ts b/identity.ts index 0b88609..2008d09 100644 --- a/identity.ts +++ b/identity.ts @@ -1,37 +1,89 @@ +/** + * This file contains the Identity algebraic data type. Identity is one of the + * simplest data types in that it represents a type AS IS, allowing one to + * create algebraic structures for the inner type. This is most useful for + * constructing arrow optics. + * + * @module Identity + * @since 2.0.0 + */ import type { Kind, Out } from "./kind.ts"; +import type { Applicable } from "./applicable.ts"; +import type { Mappable } from "./mappable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Wrappable } from "./wrappable.ts"; +/** + * @since 2.0.0 + */ export type Identity = A; +/** + * @since 2.0.0 + */ export interface KindIdentity extends Kind { readonly kind: Identity>; } +/** + * @since 2.0.0 + */ export function wrap(a: A): Identity { return a; } +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I, ): (ta: Identity) => Identity { return fai; } +/** + * @since 2.0.0 + */ export function apply( ua: Identity, ): (ufai: Identity<(a: A) => I>) => Identity { return (ufai) => ufai(ua); } +/** + * @since 2.0.0 + */ export function flatmap( fati: (a: A) => Identity, ): (ta: Identity) => Identity { return fati; } +/** + * @since 2.0.0 + */ +export const ApplicableIdentity: Applicable = { + apply, + map, + wrap, +}; + +/** + * @since 2.0.0 + */ +export const MappableIdentity: Mappable = { map }; + +/** + * @since 2.0.0 + */ export const FlatmappableIdentity: Flatmappable = { apply, map, flatmap, wrap, }; + +/** + * @since 2.0.0 + */ +export const WrappableIdentity: Wrappable = { wrap }; diff --git a/initializable.ts b/initializable.ts index 366db4f..5bbbb8a 100644 --- a/initializable.ts +++ b/initializable.ts @@ -1,5 +1,9 @@ /** - * Initializable is a structure that has a init value. It is meant to fill the + * This file contains the type and utilities for the Initializable algebraic + * data structure. A type that implements Initializable has a concept of "empty" + * represented by the init method and a concept to combine represented by the + * combine method. In other functional libraries and languages Initializable is + * called Monoid. * * @module Initializable * @since 2.0.0 @@ -9,7 +13,6 @@ import type { Combinable } from "./combinable.ts"; import type { AnyReadonlyRecord } from "./record.ts"; import * as C from "./combinable.ts"; -import { pipe, uncurry2 } from "./fn.ts"; /** * A Initializable structure has the method init. @@ -140,9 +143,9 @@ export function dual(M: Initializable): Initializable { * @since 2.0.0 */ export function intercalcate(middle: A) { - return (M: Initializable): Initializable => ({ - combine: (right) => M.combine(pipe(middle, M.combine(right))), - init: M.init, + return ({ combine, init }: Initializable): Initializable => ({ + combine: (second) => combine(combine(second)(middle)), + init, }); } /** @@ -165,6 +168,6 @@ export function intercalcate(middle: A) { export function getCombineAll( { combine, init }: Initializable, ): (...as: ReadonlyArray) => A { - const _combine = uncurry2(combine); + const _combine = (first: A, second: A) => combine(second)(first); return (...as) => as.reduce(_combine, init()); } diff --git a/iterable.ts b/iterable.ts index 850f477..0d82a8a 100644 --- a/iterable.ts +++ b/iterable.ts @@ -1,13 +1,43 @@ -import type { Kind, Out } from "./kind.ts"; -import type { Refinement } from "./refinement.ts"; -import type { Predicate } from "./predicate.ts"; -import type { Pair } from "./pair.ts"; +/** + * This file contains the Iterable algebraic data type. Iterable is a lazy, + * synchronous, and native structure defined by ecmascript. Generally, one + * interacts with the Iterator structure directly, but the Iterable type, being + * more generic, is a better target for functional. Any data structure that + * implements Iterable (ie. Array, Map, Set, etc) can use the iterable methods + * contained here if neeeded. + * + * Something to keep in mind here is that Iterables can have nasty edge cases + * that don't exist for non-lazy data structures. Specifically, an iterable can + * generate a theoretically infinite number of values, making the draining of an + * iterable potentially impossible (trying it will cause the runtime to hang). + * Additionally, many iterables are constructed using generators, which cannot + * be easily cloned. This makes the process of chaining iterables a resource + * intensive operation. As always, use these combinators with care. + * + * @module Iterable + * @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 { Either } from "./either.ts"; -import type { Option } from "./option.ts"; +import type { Filterable } from "./filterable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; +import type { Initializable } from "./initializable.ts"; +import type { 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 { Wrappable } from "./wrappable.ts"; import { isSome } from "./option.ts"; import { isLeft, isRight } from "./either.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; /** * @since 2.0.0 @@ -335,21 +365,6 @@ export function takeWhile( return takeUntil((a) => !predicate(a)); } -/** - * @since 2.0.0 - */ -export function tap( - fa: (a: A) => void, -): (ta: Iterable) => Iterable { - return (ta) => - iterable(function* () { - for (const a of ta) { - fa(a); - yield a; - } - }); -} - /** * @since 2.0.0 */ @@ -367,6 +382,62 @@ export function repeat( }); } +/** + * @since 2.0.0 + */ +export function init(): Iterable { + return iterable(function* () {}); +} + +/** + * @since 2.0.0 + */ +export function combine( + second: Iterable, +): (first: Iterable) => Iterable { + return (first) => + iterable(function* () { + for (const fst of first) { + yield fst; + } + for (const snd of second) { + yield snd; + } + }); +} + +/** + * @since 2.0.0 + */ +export function getCombinable(): Combinable> { + return { combine }; +} + +/** + * @since 2.0.0 + */ +export function getInitializable(): Initializable> { + return { init, combine }; +} + +/** + * @since 2.0.0 + */ +export function getShowable(S: Showable): Showable> { + return { + show: (ua) => `Iterable[${Array.from(ua).map(S.show).join(", ")}]`, + }; +} + +/** + * @since 2.0.0 + */ +export const ApplicableIterable: Applicable = { + apply, + map, + wrap, +}; + /** * @since 2.0.0 */ @@ -376,3 +447,47 @@ export const FlatmappableIterable: Flatmappable = { map, wrap, }; + +/** + * @since 2.0.0 + */ +export const FilterableIterable: Filterable = { + filter, + filterMap, + partition, + partitionMap, +}; + +/** + * @since 2.0.0 + */ +export const FoldableIterable: Foldable = { fold }; + +/** + * @since 2.0.0 + */ +export const MappableIterable: Mappable = { + map, +}; + +/** + * @since 2.0.0 + */ +export const WrappableIterable: Wrappable = { + wrap, +}; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableIterable); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableIterable); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(FlatmappableIterable); diff --git a/json_schema.ts b/json_schema.ts index c8061dd..5d3a079 100644 --- a/json_schema.ts +++ b/json_schema.ts @@ -3,6 +3,9 @@ * of [JSON Schema](https://json-schema.org/) in typescript. * It also includes an instance of Schemable that is useful * for describing a Schema to an external system. + * + * @module JSON Schema + * @since 2.0.0 */ import type { Kind, Out } from "./kind.ts"; @@ -185,11 +188,15 @@ export type JsonBuilder<_> = State; /** * Given a JsonBuilder this type will unwrap T for use in * type level programming. + * + * @since 2.0.0 */ export type TypeOf = T extends JsonBuilder ? A : never; /** * The Kind substitution scheme for JsonBuilder. + * + * @since 2.0.0 */ export interface KindJsonBuilder extends Kind { readonly kind: JsonBuilder>; @@ -200,20 +207,41 @@ const { combine }: Combinable = fromCombine( (second) => (first) => Object.assign({}, first, second), ); +/** + * @since 2.0.0 + */ export const FlatmappableJsonBuilder = FlatmappableState as Flatmappable< KindJsonBuilder >; +/** + * @since 2.0.0 + */ export const sequenceArray = sequenceA(FlatmappableJsonBuilder); +/** + * @since 2.0.0 + */ export const sequenceRecord = sequenceR(FlatmappableJsonBuilder); +/** + * @since 2.0.0 + */ export const wrap = FlatmappableJsonBuilder.wrap; +/** + * @since 2.0.0 + */ export const apply = FlatmappableJsonBuilder.apply; +/** + * @since 2.0.0 + */ export const map = FlatmappableJsonBuilder.map; +/** + * @since 2.0.0 + */ export const flatmap = FlatmappableJsonBuilder.flatmap; /** diff --git a/kind.ts b/kind.ts index 1516f72..fa7273e 100644 --- a/kind.ts +++ b/kind.ts @@ -19,11 +19,11 @@ * that they operate over an Array or a Set. Here is a table to illustrate this * pattern a bit more clearly. * - * | Structure | Outer Type | Inner Type | forEach Type | - * | --- | --- | --- | --- | - * | Array | Array | A | (value: A, index: number, struct: A[]) => void | - * | Set | Set | A | (value: A, index: number, struct: Set) => void | - * | Map | Map | K (key), V (value) | (value: V, key: K, struct: Map) => void | + * | Structure | Outer Type | Inner Type | forEach Type | + * | --------- | ---------- | ------------------ | ------------------------------------------------- | + * | Array | Array | A | (value: A, index: number, struct: A[]) => void | + * | Set | Set | A | (value: A, index: number, struct: Set) => void | + * | Map | Map | K (key), V (value) | (value: V, key: K, struct: Map) => void | * * In general we can see that the forEach function could have a more generic * type signiture like this: diff --git a/map.ts b/map.ts index f82f56b..635d2f8 100644 --- a/map.ts +++ b/map.ts @@ -1,10 +1,25 @@ +/** + * This file contains the ReadonlyMap algebraic data type. ReadonlyMap is a + * built in data structure in javascript that allows the association of + * arbitrary key anv value pairs. Generally, keys in a ReadonlyMap are only + * equal if their memory addresses are equal. This means that while strings and + * numbers work as expected as keys, complex objects such as [1] or { one: 1 } + * do not. Thus, there are many utility functions in this file that allow one to + * specify how to determine equality for keys in a ReadonlyMap. + * + * @module ReadonlyMap + * @since 2.0.0 + */ + import type { Kind, Out } from "./kind.ts"; import type { Bimappable } from "./bimappable.ts"; import type { Combinable } from "./combinable.ts"; import type { Comparable } from "./comparable.ts"; +import type { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; +import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; import type { Option } from "./option.ts"; -import type { Foldable } from "./foldable.ts"; import type { Showable } from "./showable.ts"; import type { Sortable } from "./sortable.ts"; @@ -14,28 +29,63 @@ import { fromCompare } from "./comparable.ts"; import { fromCombine } from "./combinable.ts"; import { flow, pipe } from "./fn.ts"; +/** + * @since 2.0.0 + */ export interface KindReadonlyMap extends Kind { readonly kind: ReadonlyMap, Out>; } +/** + * @since 2.0.0 + */ +export interface KindKeyedReadonlyMap extends Kind { + readonly kind: ReadonlyMap>; +} + +/** + * @since 2.0.0 + */ +export type TypeOf = U extends ReadonlyMap ? A : never; + +/** + * @since 2.0.0 + */ +export type KeyOf = U extends ReadonlyMap ? B : never; + +/** + * @since 2.0.0 + */ export function init(): ReadonlyMap { return new Map(); } +/** + * @since 2.0.0 + */ export function singleton(k: K, a: A): ReadonlyMap { return new Map([[k, a]]); } +/** + * @since 2.0.0 + */ export function isEmpty(ta: ReadonlyMap): boolean { return ta.size === 0; } +/** + * @since 2.0.0 + */ export function readonlyMap( ...pairs: [K, A][] ): ReadonlyMap { return new Map(pairs); } +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I, ): (ta: ReadonlyMap) => ReadonlyMap { @@ -48,6 +98,9 @@ export function map( }; } +/** + * @since 2.0.0 + */ export function bimap( fbj: (b: B) => J, fai: (a: A) => I, @@ -61,6 +114,9 @@ export function bimap( }; } +/** + * @since 2.0.0 + */ export function mapSecond( fbj: (b: B) => J, ): (ta: ReadonlyMap) => ReadonlyMap { @@ -73,10 +129,16 @@ export function mapSecond( }; } +/** + * @since 2.0.0 + */ export function size(d: ReadonlyMap): number { return d.size; } +/** + * @since 2.0.0 + */ export function lookupWithKey( S: Comparable, ): (k: K) => (ta: ReadonlyMap) => Option<[K, A]> { @@ -91,6 +153,9 @@ export function lookupWithKey( }; } +/** + * @since 2.0.0 + */ export function lookup( S: Comparable, ): (k: K) => (ta: ReadonlyMap) => Option { @@ -106,6 +171,9 @@ export function lookup( }; } +/** + * @since 2.0.0 + */ export function member( S: Comparable, ): (k: K) => (ta: ReadonlyMap) => boolean { @@ -113,6 +181,9 @@ export function member( return (k) => (m) => pipe(lookupKey(k)(m), O.isSome); } +/** + * @since 2.0.0 + */ export function elem( S: Comparable, ): (a: A) => (m: ReadonlyMap) => boolean { @@ -129,6 +200,9 @@ export function elem( }; } +/** + * @since 2.0.0 + */ export function entries( O: Sortable, ): (ta: ReadonlyMap) => ReadonlyArray<[B, A]> { @@ -136,14 +210,23 @@ export function entries( Array.from(ta.entries()).sort(([left], [right]) => O.sort(left, right)); } +/** + * @since 2.0.0 + */ export function keys(O: Sortable): (ta: ReadonlyMap) => K[] { return (ta) => Array.from(ta.keys()).sort(O.sort); } +/** + * @since 2.0.0 + */ export function values(O: Sortable): (ta: ReadonlyMap) => A[] { return (ta) => Array.from(ta.values()).sort(O.sort); } +/** + * @since 2.0.0 + */ export function fold( foldr: (accumulator: O, value: A, key: B) => O, initial: O, @@ -157,6 +240,9 @@ export function fold( }; } +/** + * @since 2.0.0 + */ export function collect( O: Sortable, ): ( @@ -170,6 +256,9 @@ export function collect( ); } +/** + * @since 2.0.0 + */ export function deleteAt( S: Comparable, ): (key: B) => (map: ReadonlyMap) => ReadonlyMap { @@ -188,6 +277,9 @@ export function deleteAt( ); } +/** + * @since 2.0.0 + */ export function insert( S: Comparable, ) { @@ -215,6 +307,9 @@ export function insert( ); } +/** + * @since 2.0.0 + */ export function insertAt( S: Comparable, ) { @@ -222,6 +317,9 @@ export function insertAt( return (key: B) => (value: A) => _insert(value)(key); } +/** + * @since 2.0.0 + */ export function modify( S: Comparable, ): ( @@ -242,6 +340,9 @@ export function modify( ); } +/** + * @since 2.0.0 + */ export function modifyAt( S: Comparable, ): ( @@ -252,18 +353,27 @@ export function modifyAt( return (key) => (modifyFn) => modify(S)(modifyFn)(key); } +/** + * @since 2.0.0 + */ export function update( S: Comparable, ): (value: A) => (key: B) => (map: ReadonlyMap) => ReadonlyMap { return (value) => (key) => modify(S)(() => value)(key); } +/** + * @since 2.0.0 + */ export function updateAt( S: Comparable, ): (key: B) => (value: A) => (map: ReadonlyMap) => ReadonlyMap { return (key) => (value) => modify(S)(() => value)(key); } +/** + * @since 2.0.0 + */ export function pop( S: Comparable, ): (b: B) => (ta: ReadonlyMap) => Option<[A, ReadonlyMap]> { @@ -280,6 +390,9 @@ export function pop( }; } +/** + * @since 2.0.0 + */ export function isSubmap( SK: Comparable, SA: Comparable, @@ -302,26 +415,40 @@ export function isSubmap( }; } -export const MappableMap: Mappable = { map }; - -export const BimappableMap: Bimappable = { map, mapSecond }; - -export const FoldableMap: Foldable = { fold }; - -export function getShowable( - SK: Showable, - SA: Showable, -): Showable> { - return ({ - show: (ta) => { - const elements = Array.from(ta).map(([k, a]) => - `[${SK.show(k)}, ${SA.show(a)}]` - ).join(", "); - return `new ReadonlyMap([${elements}])`; +/** + * @since 2.0.0 + */ +export function getFlatmappableReadonlyMap( + { init, combine }: Initializable, +): Flatmappable> { + return { + wrap: (a) => readonlyMap([init(), a]), + map, + apply: (ua) => (ufai) => { + const result = new Map(); + for (const [fkey, fai] of ufai) { + for (const [key, a] of ua) { + result.set(combine(fkey)(key), fai(a)); + } + } + return result; }, - }); + flatmap: (fuai) => (ua) => { + const result = new Map(); + for (const [key, a] of ua) { + const intermediate = fuai(a); + for (const [ikey, i] of intermediate) { + result.set(combine(ikey)(key), i); + } + } + return result; + }, + }; } +/** + * @since 2.0.0 + */ export function getComparable( SK: Comparable, SA: Comparable, @@ -332,6 +459,9 @@ export function getComparable( ); } +/** + * @since 2.0.0 + */ export function getCombinable( SK: Comparable, SA: Combinable, @@ -358,3 +488,35 @@ export function getCombinable( return r; }); } + +/** + * @since 2.0.0 + */ +export function getShowable( + SK: Showable, + SA: Showable, +): Showable> { + return ({ + show: (ta) => { + const elements = Array.from(ta).map(([k, a]) => + `[${SK.show(k)}, ${SA.show(a)}]` + ).join(", "); + return `new ReadonlyMap([${elements}])`; + }, + }); +} + +/** + * @since 2.0.0 + */ +export const MappableMap: Mappable = { map }; + +/** + * @since 2.0.0 + */ +export const BimappableMap: Bimappable = { map, mapSecond }; + +/** + * @since 2.0.0 + */ +export const FoldableMap: Foldable = { fold }; diff --git a/mod.ts b/mod.ts index 0bfa71f..9781975 100644 --- a/mod.ts +++ b/mod.ts @@ -1,3 +1,12 @@ +/** + * This file contains barrel exports for all non-contrib files in fun. + * It is generated by the `./scripts/mod.sh` file. Please do not edit + * it manually. + * + * @module Exports + * @since 2.0.0 + */ + export * as applicable from "./applicable.ts"; export * as array from "./array.ts"; export * as async from "./async.ts"; @@ -8,7 +17,6 @@ export * as boolean from "./boolean.ts"; export * as combinable from "./combinable.ts"; export * as comparable from "./comparable.ts"; export * as composable from "./composable.ts"; -export * as const from "./const.ts"; export * as decoder from "./decoder.ts"; export * as either from "./either.ts"; export * as failable from "./failable.ts"; @@ -16,7 +24,7 @@ export * as filterable from "./filterable.ts"; export * as flatmappable from "./flatmappable.ts"; export * as fn from "./fn.ts"; export * as fn_either from "./fn_either.ts"; -export * as free from "./free.ts"; +export * as foldable from "./foldable.ts"; export * as identity from "./identity.ts"; export * as initializable from "./initializable.ts"; export * as iterable from "./iterable.ts"; @@ -24,15 +32,14 @@ export * as json_schema from "./json_schema.ts"; export * as map from "./map.ts"; export * as mappable from "./mappable.ts"; export * as newtype from "./newtype.ts"; -export * as nilable from "./nilable.ts"; +export * as nil from "./nil.ts"; export * as number from "./number.ts"; -export * as optics from "./optic.ts"; +export * as optic from "./optic.ts"; export * as option from "./option.ts"; export * as pair from "./pair.ts"; export * as predicate from "./predicate.ts"; export * as premappable from "./premappable.ts"; export * as promise from "./promise.ts"; -export * as foldable from "./foldable.ts"; export * as refinement from "./refinement.ts"; export * as schemable from "./schemable.ts"; export * as set from "./set.ts"; diff --git a/newtype.ts b/newtype.ts index 1ffaeba..d6f45e4 100644 --- a/newtype.ts +++ b/newtype.ts @@ -8,6 +8,7 @@ * specified. * * @module Newtype + * @since 2.0.0 */ import type { Combinable } from "./combinable.ts"; diff --git a/nil.ts b/nil.ts new file mode 100644 index 0000000..eb3bbc4 --- /dev/null +++ b/nil.ts @@ -0,0 +1,406 @@ +/** + * This file contains the Nil algebraic data type. Nil is a native form of the + * Option algebraic data type. Instead of encapsulating a value in a tagged + * union it uses the native `undefined` and `null` types built in to javascript. + * Unfortunately, this makes its algebraic structure implementations unsound in + * some cases, specifically when one wants to use undefined or null as + * significant values (meaning they have some meaning beyond empty). + * + * @module Nil + * @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 { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; +import type { Initializable } from "./initializable.ts"; +import type { 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 { handleThrow } from "./fn.ts"; +import { fromCompare } from "./comparable.ts"; +import { fromSort } from "./sortable.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; + +/** + * @since 2.0.0 + */ +export type Nil = A | undefined | null; + +/** + * @since 2.0.0 + */ +export interface KindNil extends Kind { + readonly kind: Nil>; +} + +/** + * @since 2.0.0 + */ +export function nil(a: A): Nil { + return isNotNil(a) ? a : null; +} + +/** + * @since 2.0.0 + */ +export function init(): Nil { + return null; +} + +/** + * @since 2.0.0 + */ +export function fail(): Nil { + return null; +} + +/** + * @since 2.0.0 + */ +export function fromPredicate( + refinement: Refinement, +): (a: A) => Nil; +export function fromPredicate( + predicate: Predicate, +): (ta: Nil) => Nil; +export function fromPredicate( + predicate: Predicate, +): (ta: Nil) => Nil { + return (ta) => isNotNil(ta) && predicate(ta) ? ta : null; +} + +/** + * @since 2.0.0 + */ +export function fromOption(ua: Option): Nil { + return ua.tag === "None" ? null : ua.value; +} + +/** + * @since 2.0.0 + */ +export function tryCatch( + fda: (...d: D) => A, +): (...d: D) => Nil { + return handleThrow(fda, nil, fail); +} + +/** + * @since 2.0.0 + */ +export function match( + onNil: () => I, + onValue: (a: A) => I, +): (ta: Nil) => I { + return (ta) => (isNil(ta) ? onNil() : onValue(ta)); +} + +/** + * @since 2.0.0 + */ +export function getOrElse(onNil: () => A): (ta: Nil) => A { + return (ta) => isNil(ta) ? onNil() : ta; +} + +/** + * @since 2.0.0 + */ +export function toNull(ta: Nil): A | null { + return isNil(ta) ? null : ta; +} + +/** + * @since 2.0.0 + */ +export function toUndefined(ta: Nil): A | undefined { + return isNil(ta) ? undefined : ta; +} + +/** + * @since 2.0.0 + */ +export function isNil(ta: Nil): ta is undefined | null { + return ta === undefined || ta === null; +} + +/** + * @since 2.0.0 + */ +export function isNotNil(ta: Nil): ta is NonNullable { + return !isNil(ta); +} + +/** + * @since 2.0.0 + */ +export function wrap(a: A): Nil { + return a; +} + +/** + * @since 2.0.0 + */ +export function apply( + ua: Nil, +): (ufai: Nil<(a: A) => I>) => Nil { + return (ufai) => isNil(ua) ? null : isNil(ufai) ? null : ufai(ua); +} + +/** + * @since 2.0.0 + */ +export function map(fai: (a: A) => I): (ta: Nil) => Nil { + return (ta) => isNil(ta) ? null : fai(ta); +} + +/** + * @since 2.0.0 + */ +export function flatmap( + fati: (a: A) => Nil, +): (ta: Nil) => Nil { + return (ta) => isNil(ta) ? null : fati(ta); +} + +/** + * @since 2.0.0 + */ +export function alt(tb: Nil): (ta: Nil) => Nil { + return (ta) => isNil(ta) ? tb : ta; +} + +/** + * @since 2.0.0 + */ +export function exists(predicate: Predicate): (ua: Nil) => boolean { + return (ua) => isNotNil(ua) && predicate(ua); +} + +/** + * @since 2.0.0 + */ +export function filter( + refinement: Refinement, +): (ta: Nil) => Nil; +export function filter( + predicate: Predicate, +): (ta: Nil) => Nil; +export function filter( + predicate: Predicate, +): (ta: Nil) => Nil { + const _exists = exists(predicate); + return (ta) => _exists(ta) ? ta : null; +} + +/** + * @since 2.0.0 + */ +export function filterMap( + fai: (a: A) => Option, +): (ua: Nil) => Nil { + return flatmap((a) => fromOption(fai(a))); +} + +/** + * @since 2.0.0 + */ +export function partition( + refinement: Refinement, +): (ua: Nil) => Pair, Nil>; +export function partition( + predicate: Predicate, +): (ua: Nil) => Pair, Nil>; +export function partition( + predicate: Predicate, +): (ua: Nil) => Pair, Nil> { + type Output = Pair, Nil>; + const init: Output = [null, null]; + return (ua) => isNil(ua) ? init : predicate(ua) ? [ua, null] : [null, ua]; +} + +/** + * @since 2.0.0 + */ +export function partitionMap( + fai: (a: A) => Either, +): (ua: Nil) => Pair, Nil> { + type Output = Pair, Nil>; + const init: Output = [null, null]; + return (ua) => { + if (isNil(ua)) { + return init; + } + const result = fai(ua); + return result.tag === "Right" + ? [nil(result.right), null] + : [null, nil(result.left)]; + }; +} + +/** + * @since 2.0.0 + */ +export function traverse( + A: Applicable, +): ( + favi: (a: A) => $, +) => (ta: Nil) => $, J, K], [L], [M]> { + return ( + favi: (a: A) => $, + ): (ta: Nil) => $, J, K], [L], [M]> => + match( + () => A.wrap(fail()), + (a) => A.map((i: I) => nil(i))(favi(a)), + ); +} + +/** + * @since 2.0.0 + */ +export function fold( + reducer: (accumulator: O, current: A) => O, + initial: O, +): (ua: Nil) => O { + return (ua) => isNil(ua) ? initial : reducer(initial, ua); +} + +/** + * @since 2.0.0 + */ +export function getShowableNil({ show }: Showable): Showable> { + return { show: (ma) => (isNil(ma) ? "nil" : show(ma)) }; +} + +/** + * @since 2.0.0 + */ +export function getComparableNil( + { compare }: Comparable, +): Comparable> { + return fromCompare((second) => (first) => + isNotNil(first) && isNotNil(second) + ? compare(second)(first) + : isNil(first) && isNil(second) + ); +} + +/** + * @since 2.0.0 + */ +export function getSortableNil({ sort }: Sortable): Sortable> { + return fromSort((fst, snd) => + isNil(fst) ? isNil(snd) ? 0 : -1 : isNil(snd) ? 1 : sort(fst, snd) + ); +} + +/** + * @since 2.0.0 + */ +export function getCombinableNil( + { combine }: Combinable, +): Combinable> { + return ({ + combine: (second) => (first) => { + if (isNil(first)) { + return isNil(second) ? null : second; + } else if (isNil(second)) { + return first; + } else { + return combine(second)(first); + } + }, + }); +} + +/** + * @since 2.0.0 + */ +export function getInitializableNil( + I: Initializable, +): Initializable> { + return ({ init: () => nil(I.init()), ...getCombinableNil(I) }); +} + +/** + * @since 2.0.0 + */ +export const ApplicableNil: Applicable = { + apply, + map, + wrap, +}; + +/** + * @since 2.0.0 + */ +export const MappableNil: Mappable = { map }; + +/** + * @since 2.0.0 + */ +export const FilterableNil: Filterable = { + filter, + filterMap, + partition, + partitionMap, +}; + +/** + * @since 2.0.0 + */ +export const FlatmappableNil: Flatmappable = { + apply, + flatmap, + map, + wrap, +}; + +/** + * @since 2.0.0 + */ +export const FoldableNil: Foldable = { fold }; + +/** + * @since 2.0.0 + */ +export const TraversableNil: Traversable = { + fold, + map, + traverse, +}; + +/** + * @since 2.0.0 + */ +export const WrappableNil: Wrappable = { + wrap, +}; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableNil); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableNil); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableNil); diff --git a/nilable.ts b/nilable.ts deleted file mode 100644 index f74389f..0000000 --- a/nilable.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { Kind, Out } from "./kind.ts"; -import type { Predicate } from "./predicate.ts"; -import type { Showable } from "./showable.ts"; -import type { Flatmappable } from "./flatmappable.ts"; - -import { identity, pipe } from "./fn.ts"; - -export type Nil = undefined | null; - -export type Nilable = Nil | A; - -export interface KindNilable extends Kind { - readonly kind: Nilable>; -} - -export const nil: Nil = undefined; - -export function constNil(): Nilable { - return nil; -} - -export function make(a: A): Nilable { - return isNotNil(a) ? a : nil; -} - -export function fromPredicate( - predicate: Predicate, -): (ta: Nilable) => Nilable { - return (ta) => isNotNil(ta) && predicate(ta) ? ta : nil; -} - -export function tryCatch(fa: () => A): Nilable { - try { - return fa(); - } catch (_) { - return nil; - } -} - -export function match( - onNil: () => I, - onValue: (a: A) => I, -): (ta: Nilable) => I { - return (ta) => (isNil(ta) ? onNil() : onValue(ta)); -} - -export function getOrElse(onNil: () => A): (ta: Nilable) => A { - return (ta) => isNil(ta) ? onNil() : ta; -} - -export function toNull(ta: Nilable): A | null { - return isNil(ta) ? null : ta; -} - -export function toUndefined(ta: Nilable): A | undefined { - return isNil(ta) ? undefined : ta; -} - -export function isNil(ta: Nilable): ta is Nil { - return ta === undefined || ta === null; -} - -export function isNotNil(ta: Nilable): ta is NonNullable { - return !isNil(ta); -} - -export function wrap(a: A): Nilable { - return a; -} - -export function fail(): Nilable { - return nil; -} - -export function apply( - ua: Nilable, -): (ufai: Nilable<(a: A) => I>) => Nilable { - return (ufai) => isNil(ua) ? nil : isNil(ufai) ? nil : ufai(ua); -} - -export function map(fai: (a: A) => I): (ta: Nilable) => Nilable { - return (ta) => isNil(ta) ? nil : fai(ta); -} - -export function flatmap( - fati: (a: A) => Nilable, -): (ta: Nilable) => Nilable { - return (ta) => isNil(ta) ? nil : fati(ta); -} - -export function alt(tb: Nilable): (ta: Nilable) => Nilable { - return (ta) => isNil(ta) ? tb : ta; -} - -export const FlatmappableNilable: Flatmappable = { - apply, - flatmap, - map, - wrap, -}; - -export function getShowable({ show }: Showable): Showable> { - return { show: (ma) => (isNil(ma) ? "nil" : show(ma)) }; -} diff --git a/number.ts b/number.ts index 486c99e..7ec0505 100644 --- a/number.ts +++ b/number.ts @@ -6,12 +6,12 @@ */ import type { Ordering, Sortable } from "./sortable.ts"; -import type { Initializable } from "./initializable.ts"; +import type { Combinable } from "./combinable.ts"; import type { Comparable } from "./comparable.ts"; +import type { Initializable } from "./initializable.ts"; import type { Showable } from "./showable.ts"; import * as O from "./sortable.ts"; -import { pipe } from "./fn.ts"; /** * Compare two numbers and return true if they are equal. @@ -115,7 +115,7 @@ export function mod(second: number): (first: number) => number { * @since 2.0.0 */ export function divides(second: number): (first: number) => boolean { - return (first) => first <= second && pipe(second, mod(first)) === 0; + return (first) => first <= second && mod(first)(second) === 0; } /** @@ -225,6 +225,45 @@ export const ComparableNumber: Comparable = { compare }; */ export const SortableNumber: Sortable = O.fromSort(sort); +/** + * A Combinable instance for number that uses multiplication for combineenation. + * It contains the method combine. + * + * @since 2.0.0 + */ +export const CombinableNumberProduct: Combinable = { + combine: multiply, +}; + +/** + * A Combinable instance for number that uses addition for combineenation. + * It contains the method combine. + * + * @since 2.0.0 + */ +export const CombinableNumberSum: Combinable = { + combine: add, +}; + +/** + * A Combinable instance for number that uses Math.max for combineenation. + * It contains the method combine. + * + * @since 2.0.0 + */ +export const CombinableNumberMax: Combinable = { + combine: O.max(SortableNumber), +}; + +/** + * A Combinable instance for number that uses Math.min for combineenation. + * It contains the method combine. + * + * @since 2.0.0 + */ +export const CombinableNumberMin: Combinable = { + combine: O.min(SortableNumber), +}; /** * A Initializable instance for number that uses multiplication for combineenation. * It contains the method combine. diff --git a/optic.ts b/optic.ts index 5ee7723..270d3b5 100644 --- a/optic.ts +++ b/optic.ts @@ -24,10 +24,10 @@ * and profunctor optics, and is much more compact and performant in typescript * than those implementations. * - * @module Optics - * + * @module Optic * @since 2.0.0 */ + import type { $, Kind } from "./kind.ts"; import type { Comparable } from "./comparable.ts"; import type { Initializable } from "./initializable.ts"; @@ -50,7 +50,7 @@ import * as M from "./map.ts"; import * as P from "./pair.ts"; import { TraversableSet } from "./set.ts"; import { TraversableTree } from "./tree.ts"; -import { isNotNil } from "./nilable.ts"; +import { isNotNil } from "./nil.ts"; import { getCombineAll } from "./initializable.ts"; import { dimap, flow, identity, over, pipe } from "./fn.ts"; @@ -931,82 +931,6 @@ export function imap( return compose(iso(fai, fia)); } -/** - * Map over the Viewer portion of an optic. This effectively uses the map from - * the Flatmappable associated with the tag of the optic. - * - * @example - * ```ts - * import * as O from "./optic.ts"; - * import { pipe } from "./fn.ts"; - * - * const mapped = pipe( - * O.id>(), - * O.index(1), - * O.map(str => str.length), - * ); - * - * const result1 = mapped.view(["Hello", "World"]); // Some(5) - * const result2 = mapped.view([]); // None - * ``` - * - * @since 2.0.0 - */ -export function map( - fai: (a: A) => I, -): (first: Viewer) => Viewer { - return ({ tag, view }) => { - const _map = getFlatmappable(tag).map; - return viewer(tag, flow(view, _map(fai))); - }; -} - -/** - * Apply the value returned by a Viewer to a function returned by a Viewer. - * - * @example - * ```ts - * import * as O from "./optic.ts"; - * import { pipe } from "./fn.ts"; - * - * type Person = { name: string, age: number }; - * type State = { people: readonly Person[], format: (p: Person) => string }; - * - * const fmt = pipe(O.id(), O.prop("format")); - * const adults = pipe( - * O.id(), - * O.prop("people"), - * O.array, - * O.filter(p => p.age > 18) - * ); - * - * const formatted = pipe(fmt, O.apply(adults)); - * - * const result = formatted.view({ - * people: [ - * { name: "Brandon", age: 37 }, - * { name: "Rufus", age: 1 }, - * ], - * format: p => `${p.name} is ${p.age}`, - * }); // [ "Brandon is 37" ] - * ``` - * - * @since 2.0.0 - */ -export function apply( - second: Viewer | Optic, -): ( - first: Viewer I> | Optic I>, -) => Viewer, S, I> { - return (first) => { - const tag = align(first.tag, second.tag); - const _ap = getFlatmappable(tag).apply; - const _first = _unsafeCast(first, tag); - const _second = _unsafeCast(second, tag); - return viewer(tag, (s) => pipe(_first(s), _ap(_second(s)))); - }; -} - /** * A composable combinator that focuses on a property P of a struct. * @@ -1346,7 +1270,7 @@ export function traverse( export function combineAll( initializable: Initializable, fai: (a: A) => I, -) { +): (first: Optic) => (s: S) => I { const _combineAll = getCombineAll(initializable); return (first: Optic): (s: S) => I => { const view = _unsafeCast(first, FoldTag); @@ -1453,7 +1377,7 @@ export const tree: ( * * type Input = { value?: string | null }; * - * const value = pipe(O.id(), O.prop("value"), O.nilable); + * const value = pipe(O.id(), O.prop("value"), O.nil); * * const result1 = pipe(value, O.view({})); // None * const result2 = pipe(value, O.view({ value: "Hello" })); // Some("Hello") @@ -1461,7 +1385,7 @@ export const tree: ( * * @since 2.0.0 */ -export const nilable: ( +export const nil: ( first: Optic, ) => Optic, S, NonNullable> = filter(isNotNil); diff --git a/option.ts b/option.ts index 73a9161..cc50966 100644 --- a/option.ts +++ b/option.ts @@ -9,6 +9,7 @@ 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"; @@ -24,10 +25,12 @@ import type { Sortable } from "./sortable.ts"; import type { Traversable } from "./traversable.ts"; import type { Wrappable } from "./wrappable.ts"; -import { isNotNil } from "./nilable.ts"; +import { isNotNil } from "./nil.ts"; import { fromCompare } from "./comparable.ts"; import { fromSort } from "./sortable.ts"; import { flow, handleThrow, pipe } from "./fn.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; /** * The None type represents the non-existence of a value. @@ -670,69 +673,6 @@ export function traverse( ); } -/** - * The canonical implementation of Wrappable for Option. - * - * @since 2.0.0 - */ -export const WrappableOption: Wrappable = { wrap }; - -/** - * The canonical implementation of Mappable for Option. - * - * @since 2.0.0 - */ -export const MappableOption: Mappable = { map }; - -/** - * The canonical implementation of Filterable for Option. - * - * @since 2.0.0 - */ -export const FilterableOption: Filterable = { - filter, - filterMap, - partition, - partitionMap, -}; - -/** - * The canonical implementation of Foldable for Option. - * - * @since 2.0.0 - */ -export const FoldableOption: Foldable = { fold }; - -/** - * The canonical implementation of Traversable for Option. - * - * @since 2.0.0 - */ -export const TraversableOption: Traversable = { - map, - fold, - traverse, -}; - -/** - * The canonical implementation of Applicable for Option. - * - * @since 2.0.0 - */ -export const ApplicableOption: Applicable = { wrap, map, apply }; - -/** - * The canonical implementation of Flatmappable for Option. - * - * @since 2.0.0 - */ -export const FlatmappableOption: Flatmappable = { - wrap, - map, - apply, - flatmap, -}; - /** * Create an instance of Showable for Option given an instance of Showable for A. * @@ -740,7 +680,7 @@ export const FlatmappableOption: Flatmappable = { * ```ts * import * as O from "./option.ts"; * - * const Showable = O.getShowable({ show: (n: number) => n.toString() }); // Showable> + * const Showable = O.getShowableOption({ show: (n: number) => n.toString() }); // Showable> * * const result1 = Showable.show(O.some(1)); // "Some(1)" * const result2 = Showable.show(O.none); // "None" @@ -748,7 +688,7 @@ export const FlatmappableOption: Flatmappable = { * * @since 2.0.0 */ -export function getShowable( +export function getShowableOption( { show }: Showable, ): Showable> { return ({ @@ -765,7 +705,7 @@ export function getShowable( * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * - * const { compare } = O.getComparable(N.ComparableNumber); + * const { compare } = O.getComparableOption(N.ComparableNumber); * * const result1 = pipe(O.some(1), compare(O.some(2))); // false * const result2 = pipe(O.some(1), compare(O.some(1))); // true @@ -775,7 +715,7 @@ export function getShowable( * * @since 2.0.0 */ -export function getComparable( +export function getComparableOption( { compare }: Comparable, ): Comparable> { return fromCompare((second) => (first) => @@ -793,7 +733,7 @@ export function getComparable( * import * as O from "./option.ts"; * import * as N from "./number.ts"; * - * const { sort } = O.getSortable(N.SortableNumber); + * const { sort } = O.getSortableOption(N.SortableNumber); * * const result1 = sort(O.some(1), O.some(2)); // 1 * const result2 = sort(O.some(1), O.some(1)); // 0 @@ -803,7 +743,9 @@ export function getComparable( * * @since 2.0.0 */ -export function getSortable({ sort }: Sortable): Sortable> { +export function getSortableOption( + { sort }: Sortable, +): Sortable> { return fromSort((fst, snd) => isNone(fst) ? isNone(snd) ? 0 : -1 @@ -813,6 +755,22 @@ export function getSortable({ sort }: Sortable): Sortable> { ); } +/** + * @since 2.0.0 + */ +export function getCombinableOption( + { combine }: Combinable, +): Combinable> { + return { + combine: (second) => (first) => + isNone(first) + ? second + : isNone(second) + ? first + : wrap(combine(second.value)(first.value)), + }; +} + /** * Create an instance of Initializable> given an instance of Initializable. * @@ -822,7 +780,7 @@ export function getSortable({ sort }: Sortable): Sortable> { * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * - * const { combine } = O.getInitializable(N.InitializableNumberSum); + * const { combine } = O.getInitializableOption(N.InitializableNumberSum); * * const result1 = pipe(O.some(1), combine(O.some(1))); // Some(2) * const result2 = pipe(O.none, combine(O.some(1))); // Some(1) @@ -831,16 +789,89 @@ export function getSortable({ sort }: Sortable): Sortable> { * * @since 2.0.0 */ -export function getInitializable( - { combine }: Initializable, +export function getInitializableOption( + I: Initializable, ): Initializable> { return ({ - init: () => none, - combine: (second) => (first) => - isNone(first) - ? second - : isNone(second) - ? first - : wrap(combine(second.value)(first.value)), + init: () => some(I.init()), + ...getCombinableOption(I), }); } + +/** + * The canonical implementation of Applicable for Option. + * + * @since 2.0.0 + */ +export const ApplicableOption: Applicable = { wrap, map, apply }; + +/** + * The canonical implementation of Filterable for Option. + * + * @since 2.0.0 + */ +export const FilterableOption: Filterable = { + filter, + filterMap, + partition, + partitionMap, +}; + +/** + * The canonical implementation of Foldable for Option. + * + * @since 2.0.0 + */ +export const FoldableOption: Foldable = { fold }; + +/** + * The canonical implementation of Flatmappable for Option. + * + * @since 2.0.0 + */ +export const FlatmappableOption: Flatmappable = { + wrap, + map, + apply, + flatmap, +}; + +/** + * The canonical implementation of Mappable for Option. + * + * @since 2.0.0 + */ +export const MappableOption: Mappable = { map }; + +/** + * The canonical implementation of Traversable for Option. + * + * @since 2.0.0 + */ +export const TraversableOption: Traversable = { + map, + fold, + traverse, +}; + +/** + * The canonical implementation of Wrappable for Option. + * + * @since 2.0.0 + */ +export const WrappableOption: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableOption); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableOption); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableOption); diff --git a/pair.ts b/pair.ts index a1bf662..3dbdd2d 100644 --- a/pair.ts +++ b/pair.ts @@ -9,11 +9,14 @@ import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Bimappable } from "./bimappable.ts"; +import type { Combinable } from "./combinable.ts"; +import type { Comparable } from "./comparable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; -import type { Foldable } from "./foldable.ts"; import type { Showable } from "./showable.ts"; +import type { Sortable } from "./sortable.ts"; import type { Traversable } from "./traversable.ts"; import { createFlatmappable } from "./flatmappable.ts"; @@ -34,6 +37,8 @@ import { pipe } from "./fn.ts"; * * Other uses will likely come when Arrows * are implemented in fun. + * + * @since 2.0.0 */ export type Pair = readonly [A, B]; @@ -389,45 +394,74 @@ export function traverse(A: Applicable) { } /** - * The canonical Mappable instance for Pair. Contains the - * map method. + * Creates a Showable instance for a pair, wrapping the Showable instances provided + * for the first and second values. * * @since 2.0.0 */ -export const MappablePair: Mappable = { map }; +export function getShowablePair( + SA: Showable, + SB: Showable, +): Showable> { + return { + show: ([first, second]) => `Pair(${SA.show(first)}, ${SB.show(second)})`, + }; +} /** - * The canonical Bimappable instance for Pair. Contains the - * bimap and mapSecond methods. - * * @since 2.0.0 */ -export const BimappablePair: Bimappable = { mapSecond, map }; +export function getCombinablePair( + CA: Combinable, + CB: Combinable, +): Combinable> { + return { + combine: (second) => (first) => [ + CA.combine(second[0])(first[0]), + CB.combine(second[1])(first[1]), + ], + }; +} /** - * The canonical Foldable instance for Pair. Contains the - * fold method. - * * @since 2.0.0 */ -export const FoldablePair: Foldable = { fold }; +export function getInitializablePair( + IA: Initializable, + IB: Initializable, +): Initializable> { + return { + init: () => [IA.init(), IB.init()], + ...getCombinablePair(IA, IB), + }; +} /** * @since 2.0.0 */ -export const TraversablePair: Traversable = { map, fold, traverse }; +export function getComparablePair( + CA: Comparable, + CB: Comparable, +): Comparable> { + return { + compare: (second) => (first) => + CA.compare(second[0])(first[0]) && CB.compare(second[1])(first[1]), + }; +} /** - * Creates a Showable instance for a pair, wrapping the Showable instances provided - * for the first and second values. + * @since 2.0.0 */ -export function getShowable( - SA: Showable, - SB: Showable, -): Showable> { - return { - show: ([first, second]) => `Pair(${SA.show(first)}, ${SB.show(second)})`, - }; +export function getSortablePair( + SA: Sortable, + SB: Sortable, +): Sortable> { + return ({ + sort: (first, second) => { + const oa = SA.sort(first[0], second[0]); + return oa === 0 ? SB.sort(first[1], second[1]) : oa; + }, + }); } /** @@ -473,3 +507,32 @@ export function getRightFlatmappable( pipe(fati(first), mapSecond(combine(second))), }); } + +/** + * The canonical Mappable instance for Pair. Contains the + * map method. + * + * @since 2.0.0 + */ +export const MappablePair: Mappable = { map }; + +/** + * The canonical Bimappable instance for Pair. Contains the + * bimap and mapSecond methods. + * + * @since 2.0.0 + */ +export const BimappablePair: Bimappable = { mapSecond, map }; + +/** + * The canonical Foldable instance for Pair. Contains the + * fold method. + * + * @since 2.0.0 + */ +export const FoldablePair: Foldable = { fold }; + +/** + * @since 2.0.0 + */ +export const TraversablePair: Traversable = { map, fold, traverse }; diff --git a/predicate.ts b/predicate.ts index c6a99cb..618d50e 100644 --- a/predicate.ts +++ b/predicate.ts @@ -9,7 +9,7 @@ */ import type { In, Kind } from "./kind.ts"; -import type { Premappable } from "./premappable.ts"; +import type { Combinable } from "./combinable.ts"; import type { Initializable } from "./initializable.ts"; import { flow } from "./fn.ts"; @@ -159,14 +159,18 @@ export function and(second: Predicate) { } /** - * The canonical implementation of Premappable for Predicate. It contains - * the method premap. - * * @since 2.0.0 */ -export const PremappablePredicate: Premappable = { - premap, -}; +export function getCombinableAny(): Combinable> { + return { combine: or }; +} + +/** + * @since 2.0.0 + */ +export function getCombinableAll(): Combinable> { + return { combine: and }; +} /** * Get a Initializable> for any type A that combines using the diff --git a/premappable.ts b/premappable.ts index 7fba683..2c18d40 100644 --- a/premappable.ts +++ b/premappable.ts @@ -9,29 +9,19 @@ import type { $, Hold, Kind } from "./kind.ts"; import type { Mappable } from "./mappable.ts"; -import { flow } from "./fn.ts"; - /** * A Premappable structure has the method premap. * * @since 2.0.0 */ -export interface Premappable extends Hold { +export interface Premappable extends Mappable, Hold { readonly premap: ( fia: (l: L) => D, ) => (ua: $) => $; -} - -/** - * @since 2.0.0 - */ -export function dimap( - { map, premap }: Premappable & Mappable, -): ( - fld: (l: L) => D, - fai: (a: A) => I, -) => ( - ua: $, -) => $ { - return (fld, fai) => flow(premap(fld), map(fai)); + readonly dimap: ( + fld: (l: L) => D, + fai: (a: A) => I, + ) => ( + ua: $, + ) => $; } diff --git a/promise.ts b/promise.ts index 6ebafaa..4836b27 100644 --- a/promise.ts +++ b/promise.ts @@ -1,11 +1,24 @@ +/** + * This file contains the Promise algebraic data type. Promise is the javascript + * built in data structure for asynchronous computation. + * + * @module Promise + * @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 { Either } from "./either.ts"; +import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Wrappable } from "./wrappable.ts"; import * as E from "./either.ts"; import { flow, handleThrow, pipe } from "./fn.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; /** * A type for Promise over any, useful as an extension target for @@ -73,7 +86,8 @@ export function deferred(): Deferred { * the returned promise does resolve when the abort signal * occurs, the existing promise continues running in the * background. For this reason it is important to - * catch any errors associated with the original promise. + * catch any errors associated with the original promise and + * to not implement side effects in the aborted promise. * * @example * ```ts @@ -99,7 +113,7 @@ export function deferred(): Deferred { export function abortable( signal: AbortSignal, onAbort: (reason: unknown) => B, -) { +): (ua: Promise) => Promise> { return (ua: Promise): Promise> => { if (signal.aborted) { return resolve(E.left(onAbort(signal.reason))); @@ -388,6 +402,13 @@ export function flatmap( return then(faui); } +/** + * @since 2.0.0 + */ +export function fail(b: unknown): Promise { + return reject(b); +} + /** * Wrap a function that potentially throws in a try/catch block, * handling any thrown errors and returning the result inside @@ -425,12 +446,27 @@ export function tryCatch( } /** - * The canonical implementation of Mappable for Promise. It contains - * the method map. - * * @since 2.0.0 */ -export const MappablePromise: Mappable = { map }; +export function getCombinablePromise( + { combine }: Combinable, +): Combinable> { + return { + combine: (second) => async (first) => combine(await second)(await first), + }; +} + +/** + * @since 2.0.0 + */ +export function getInitializablePromise( + I: Initializable, +): Initializable> { + return { + init: () => resolve(I.init()), + ...getCombinablePromise(I), + }; +} /** * The canonical implementation of Applicable for Promise. It contains @@ -440,6 +476,14 @@ export const MappablePromise: Mappable = { map }; */ export const ApplicablePromise: Applicable = { wrap, map, apply }; +/** + * The canonical implementation of Mappable for Promise. It contains + * the method map. + * + * @since 2.0.0 + */ +export const MappablePromise: Mappable = { map }; + /** * The canonical implementation of Flatmappable for Promise. It contains * the methods wrap, apply, map, and flatmap. @@ -452,3 +496,23 @@ export const FlatmappablePromise: Flatmappable = { map, wrap, }; + +/** + * @since 2.0.0 + */ +export const WrappablePromise: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappablePromise); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappablePromise); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappablePromise); diff --git a/record.ts b/record.ts index 2ce1f40..37108d0 100644 --- a/record.ts +++ b/record.ts @@ -1,4 +1,3 @@ -// deno-lint-ignore-file no-explicit-any /** * ReadonlyRecord is a readonly product structure that operates * like a Map. Keys are always strings and Key/Value pairs @@ -6,16 +5,20 @@ * type in fun favors immutability. * * @module ReadonlyRecord + * @since 2.0.0 */ +// deno-lint-ignore-file no-explicit-any + import type { $, AnySub, Intersect, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; -import type { Either } from "./either.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 { Foldable } from "./foldable.ts"; -import type { Mappable } from "./mappable.ts"; import type { Initializable } from "./initializable.ts"; +import type { Mappable } from "./mappable.ts"; import type { Option } from "./option.ts"; import type { Pair } from "./pair.ts"; import type { Showable } from "./showable.ts"; @@ -930,6 +933,81 @@ export function partitionMap( }; } +/** + * @since 2.0.0 + */ +export function getCombinableRecord( + { combine }: Combinable, +): Combinable> { + return { + combine: (second) => (first) => { + const result: Record = { ...first }; + for (const [key, value] of entries(second)) { + if (key in result) { + result[key] = combine(value)(result[key]); + } else { + result[key] = value; + } + } + return result; + }, + }; +} + +/** + * @since 2.0.0 + */ +export function getInitializableRecord( + I: Initializable, +): Initializable> { + return { + init: () => ({}), + ...getCombinableRecord(I), + }; +} + +/** + * @since 2.0.0 + */ +export function getComparableRecord( + C: Comparable, +): Comparable> { + const _isSubrecord = isSubrecord(C); + return { + compare: (second) => (first) => + _isSubrecord(second)(first) && _isSubrecord(first)(second), + }; +} + +/** + * Given a Showable for the inner values of a ReadonlyRecord, return an instance + * of Showable for ReadonlyRecord. + * + * @example + * ```ts + * import * as R from "./record.ts"; + * import { ShowableNumber } from "./number.ts"; + * + * const { show } = R.getShowableRecord(ShowableNumber); + * + * const result = show({ one: 1, two: 2, three: 3 }); + * // "{one: 1, two: 2, three: 3}" + * ``` + * + * @since 2.0.0 + */ +export function getShowableRecord( + SA: Showable, +): Showable> { + return ({ + show: (ua) => + `{${ + Object.entries(ua).map(([key, value]) => `${key}: ${SA.show(value)}`) + .join(", ") + }}`, + }); +} + /** * The canonical implementation of Filterable for ReadonlyRecord. It contains * the methods filter, filterMap, partition, and partitionMap. @@ -970,30 +1048,3 @@ export const TraversableRecord: Traversable = { fold, traverse, }; - -/** - * Given a Showable for the inner values of a ReadonlyRecord, return an instance - * of Showable for ReadonlyRecord. - * - * @example - * ```ts - * import * as R from "./record.ts"; - * import { ShowableNumber } from "./number.ts"; - * - * const { show } = R.getShowable(ShowableNumber); - * - * const result = show({ one: 1, two: 2, three: 3 }); - * // "{one: 1, two: 2, three: 3}" - * ``` - * - * @since 2.0.0 - */ -export function getShowable(SA: Showable): Showable> { - return ({ - show: (ua) => - `{${ - Object.entries(ua).map(([key, value]) => `${key}: ${SA.show(value)}`) - .join(", ") - }}`, - }); -} diff --git a/reducible.ts b/reducible.ts deleted file mode 100644 index 53df5f4..0000000 --- a/reducible.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Reducible is a structure that allows a function to all values inside of a - * structure. The reduction is accumulative and ordered. - * - * @module Reducible - * @since 2.0.0 - */ - -import type { $, Hold, Kind } from "./kind.ts"; -import type { Initializable } from "./initializable.ts"; - -/** - * A Reducible structure has the method reduce. - * - * @since 2.0.0 - */ -export interface Reducible extends Hold { - readonly reduce: ( - reducer: (accumulator: O, value: A) => O, - accumulator: O, - ) => ( - ua: $, - ) => O; -} - -/** - * @experimental - * @since 2.0.0 - */ -export function collect( - { reduce }: Reducible, - { combine, init }: Initializable, -): ( - ua: $, -) => A { - return (ua) => - reduce((first, second) => combine(second)(first), init())(ua); -} diff --git a/refinement.ts b/refinement.ts index f34db9b..b8d1cc7 100644 --- a/refinement.ts +++ b/refinement.ts @@ -8,6 +8,7 @@ * @module Refinement * @since 2.0.0 */ + import type { In, Kind, Out } from "./kind.ts"; import type { NonEmptyArray } from "./array.ts"; diff --git a/scripts/mod.sh b/scripts/mod.sh index 0f5d5f9..67f2681 100755 --- a/scripts/mod.sh +++ b/scripts/mod.sh @@ -1,3 +1,15 @@ #!/usr/bin/env zsh +cat << EOF +/** + * This file contains barrel exports for all non-contrib files in fun. + * It is generated by the \`./scripts/mod.sh\` file. Please do not edit + * it manually. + * + * @module Exports + * @since 2.0.0 + */ + +EOF + for i in *[!(mod)].ts; do echo "export * as ${i:s/\.ts//} from \"./$i\";"; done diff --git a/set.ts b/set.ts index f2ded73..bd4838f 100644 --- a/set.ts +++ b/set.ts @@ -3,27 +3,32 @@ * and it operates on object equality for deduplication. * * @module ReadonlySet + * @since 2.0.0 */ import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; -import type { Either } from "./either.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 { Flatmappable } from "./flatmappable.ts"; import type { Foldable } from "./foldable.ts"; +import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; -import type { Flatmappable } from "./flatmappable.ts"; -import type { Combinable } from "./combinable.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 { Traversable } from "./traversable.ts"; +import type { Wrappable } from "./wrappable.ts"; import { flow, identity, pipe } from "./fn.ts"; import { fromCompare } from "./comparable.ts"; import { fromCombine } from "./combinable.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; import * as O from "./option.ts"; import * as E from "./either.ts"; @@ -742,68 +747,6 @@ export function traverse( ); } -/** - * The canonical implementation of Mappable for ReadonlySet. It contains - * the method map. - * - * @since 2.0.0 - */ -export const MappableSet: Mappable = { map }; - -/** - * The canonical implementation of Applicable for ReadonlySet. It contains - * the methods wrap, ap, and map. - * - * @since 2.0.0 - */ -export const ApplicableSet: Applicable = { apply, map, wrap }; - -/** - * The canonical implementation of Flatmappable for ReadonlySet. It contains - * the methods wrap, ap, map, join, and flatmap. - * - * @since 2.0.0 - */ -export const FlatmappableSet: Flatmappable = { - apply, - flatmap, - map, - wrap, -}; - -/** - * The canonical implementation of Filterable for ReadonlySet. It contains - * the methods filter, filterMap, partition, and partitionMap. - * - * @since 2.0.0 - */ -export const FilterableSet: Filterable = { - filter, - filterMap, - partition, - partitionMap, -}; - -/** - * The canonical implementation of Foldable for ReadonlySet. It contains - * the method fold. - * - * @since 2.0.0 - */ -export const FoldableSet: Foldable = { fold }; - -/** - * The canonical implementation of Traversable for ReadonlySet. It contains - * the methods map, fold, and traverse. - * - * @since 2.0.0 - */ -export const TraversableSet: Traversable = { - map, - fold, - traverse, -}; - /** * Given an instance of Showable return an instance of Showable>. * @@ -812,7 +755,7 @@ export const TraversableSet: Traversable = { * import * as S from "./set.ts"; * import * as N from "./number.ts"; * - * const { show } = S.getShowable(N.ShowableNumber); + * const { show } = S.getShowableSet(N.ShowableNumber); * * const result1 = show(S.init()); // "Set([])" * const result2 = show(S.set(1, 2, 3)); // "Set([1, 2, 3])" @@ -820,7 +763,7 @@ export const TraversableSet: Traversable = { * * @since 2.0.0 */ -export function getShowable(S: Showable): Showable> { +export function getShowableSet(S: Showable): Showable> { return ({ show: (s) => `Set([${Array.from(s.values()).map(S.show).join(", ")}])`, }); @@ -835,7 +778,7 @@ export function getShowable(S: Showable): Showable> { * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * - * const { compare } = S.getComparable(N.ComparableNumber); + * const { compare } = S.getComparableSet(N.ComparableNumber); * * const result1 = compare( * S.set(1, 2, 3))( @@ -849,7 +792,9 @@ export function getShowable(S: Showable): Showable> { * * @since 2.0.0 */ -export function getComparable(S: Comparable): Comparable> { +export function getComparableSet( + S: Comparable, +): Comparable> { const subset = isSubset(S); return fromCompare((second) => (first) => subset(first)(second) && subset(second)(first) @@ -867,7 +812,7 @@ export function getComparable(S: Comparable): Comparable> { * import * as M from "./combinable.ts"; * import { pipe } from "./fn.ts"; * - * const monoid = S.getUnionCombinable(N.ComparableNumber); + * const monoid = S.getCombinableSet(N.ComparableNumber); * const combineAll = M.getCombineAll(monoid); * * const result1 = combineAll( @@ -880,8 +825,123 @@ export function getComparable(S: Comparable): Comparable> { * * @since 2.0.0 */ -export function getUnionCombinable( +export function getCombinableSet( S: Comparable, ): Combinable> { return fromCombine(union(S)); } + +/** + * Given an instance of Comparable create a Combinable> where + * combine creates a union of two ReadonlySets. + * + * @example + * ```ts + * import * as S from "./set.ts"; + * import * as N from "./number.ts"; + * import * as M from "./combinable.ts"; + * import { pipe } from "./fn.ts"; + * + * const monoid = S.getInitializableSet(N.ComparableNumber); + * const combineAll = M.getCombineAll(monoid); + * + * const result1 = combineAll( + * S.set(1, 2, 3), + * S.set(4, 5, 6), + * S.set(1, 3, 5, 7) + * ); // Set(1, 2, 3, 4, 5, 6, 7) + * const result3 = combineAll(S.init()); // Set() + * ``` + * + * @since 2.0.0 + */ +export function getInitializableSet( + C: Comparable, +): Initializable> { + return { + init: () => new Set(), + ...getCombinableSet(C), + }; +} + +/** + * The canonical implementation of Applicable for ReadonlySet. It contains + * the methods wrap, ap, and map. + * + * @since 2.0.0 + */ +export const ApplicableSet: Applicable = { apply, map, wrap }; + +/** + * The canonical implementation of Filterable for ReadonlySet. It contains + * the methods filter, filterMap, partition, and partitionMap. + * + * @since 2.0.0 + */ +export const FilterableSet: Filterable = { + filter, + filterMap, + partition, + partitionMap, +}; + +/** + * The canonical implementation of Flatmappable for ReadonlySet. It contains + * the methods wrap, ap, map, join, and flatmap. + * + * @since 2.0.0 + */ +export const FlatmappableSet: Flatmappable = { + apply, + flatmap, + map, + wrap, +}; + +/** + * The canonical implementation of Foldable for ReadonlySet. It contains + * the method fold. + * + * @since 2.0.0 + */ +export const FoldableSet: Foldable = { fold }; + +/** + * The canonical implementation of Mappable for ReadonlySet. It contains + * the method map. + * + * @since 2.0.0 + */ +export const MappableSet: Mappable = { map }; + +/** + * The canonical implementation of Traversable for ReadonlySet. It contains + * the methods map, fold, and traverse. + * + * @since 2.0.0 + */ +export const TraversableSet: Traversable = { + map, + fold, + traverse, +}; + +/** + * @since 2.0.0 + */ +export const WrappableSet: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableSet); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableSet); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableSet); diff --git a/showable.ts b/showable.ts index 57901a3..15e3082 100644 --- a/showable.ts +++ b/showable.ts @@ -5,6 +5,7 @@ * @module Showable * @since 2.0.0 */ + import type { Hold } from "./kind.ts"; /** diff --git a/sortable.ts b/sortable.ts index a63771d..3212545 100644 --- a/sortable.ts +++ b/sortable.ts @@ -3,9 +3,10 @@ * composing Sortables. Since an Sortable encapsulates partial * equality, the tools in this file should concern * itself with sorting according to an Ordering as well + * + * @since 2.0.0 */ -import type { Premappable } from "./premappable.ts"; import type { Hold, In, Kind } from "./kind.ts"; /** @@ -450,11 +451,3 @@ export function premap( ): ({ sort }: Sortable) => Sortable { return ({ sort }) => fromSort((fst, snd) => sort(fld(fst), fld(snd))); } - -/** - * The canonical implementation of Premappable for Sortable. It contains - * the method premap. - * - * @since 2.0.0 - */ -export const PremappableSortable: Premappable = { premap }; diff --git a/state.ts b/state.ts index 239585f..8033810 100644 --- a/state.ts +++ b/state.ts @@ -4,14 +4,21 @@ * purity, so that subsequent executions of a state workflow with the same * initial conditions produce the exact same results. * - * @since 2.0.0 - * * @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 { Flatmappable } from "./flatmappable.ts"; +import type { Initializable } from "./initializable.ts"; +import type { Mappable } from "./mappable.ts"; +import type { Wrappable } from "./wrappable.ts"; -import { flow, identity, pipe } from "./fn.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 @@ -285,6 +292,44 @@ 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. @@ -297,3 +342,28 @@ export const FlatmappableState: Flatmappable = { 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 = createTap(FlatmappableState); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableState); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableState); diff --git a/string.ts b/string.ts index 9a1b373..b0384dd 100644 --- a/string.ts +++ b/string.ts @@ -1,8 +1,19 @@ +/** + * This file contains the String algebraic data type. String is a the built in + * javascript string structure. This file contains data last implementations of + * most of the String built in methods as well as minor "fixes" to make them + * less error prone. + * + * @module String + * @since 2.0.0 + */ + +import type { Combinable } from "./combinable.ts"; +import type { Comparable } from "./comparable.ts"; import type { Initializable } from "./initializable.ts"; import type { NonEmptyArray } from "./array.ts"; import type { Option } from "./option.ts"; import type { Ordering, Sortable } from "./sortable.ts"; -import type { Comparable } from "./comparable.ts"; import type { Showable } from "./showable.ts"; import { fromCompare } from "./comparable.ts"; @@ -451,6 +462,13 @@ export function test(r: RegExp) { */ export const SortableString: Sortable = fromSort(sort); +/** + * @since 2.0.0 + */ +export const CombinableString: Combinable = { + combine, +}; + /** * The canonical implementation of Comparable for string. It contains * the method equals. diff --git a/sync.ts b/sync.ts index 2508b03..3db7439 100644 --- a/sync.ts +++ b/sync.ts @@ -1,37 +1,71 @@ +/** + * This file contains the Sync algebraic data type. Sync is an lazy function + * that takes no inputs. In other functional languages and runtimes sync is + * referred to as IO. + * + * @module Sync + * @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 { Flatmappable } from "./flatmappable.ts"; +import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; import type { Foldable } from "./foldable.ts"; import type { Traversable } from "./traversable.ts"; import type { Wrappable } from "./wrappable.ts"; import { constant, flow, pipe } from "./fn.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; +/** + * @since 2.0.0 + */ export type Sync = () => A; +/** + * @since 2.0.0 + */ export interface KindSync extends Kind { readonly kind: Sync>; } +/** + * @since 2.0.0 + */ export function wrap(a: A): Sync { return constant(a); } +/** + * @since 2.0.0 + */ export function apply(ua: Sync): (ta: Sync<(a: A) => I>) => Sync { return (ufai) => flow(ua, ufai()); } +/** + * @since 2.0.0 + */ export function map(fai: (a: A) => I): (ta: Sync) => Sync { return (ta) => flow(ta, fai); } +/** + * @since 2.0.0 + */ export function flatmap( fati: (a: A) => Sync, ): (ta: Sync) => Sync { return (ta) => flow(ta, fati, (x) => x()); } +/** + * @since 2.0.0 + */ export function fold( foao: (o: O, a: A) => O, o: O, @@ -39,6 +73,9 @@ export function fold( return (ta) => foao(o, ta()); } +/** + * @since 2.0.0 + */ export function traverse( A: Applicable & Mappable, ): ( @@ -47,12 +84,37 @@ export function traverse( return (faui) => (ta) => pipe(faui(ta()), A.map(wrap)); } -export const WrappableSync: Wrappable = { wrap }; +/** + * @since 2.0.0 + */ +export function getCombinableSync( + { combine }: Combinable, +): Combinable> { + return { + combine: (second) => (first) => () => combine(second())(first()), + }; +} -export const ApplicableSync: Applicable = { apply, map, wrap }; +/** + * @since 2.0.0 + */ +export function getInitializableSync( + I: Initializable, +): Initializable> { + return { + init: () => I.init, + ...getCombinableSync(I), + }; +} -export const MappableSync: Mappable = { map }; +/** + * @since 2.0.0 + */ +export const ApplicableSync: Applicable = { apply, map, wrap }; +/** + * @since 2.0.0 + */ export const FlatmappableSync: Flatmappable = { apply, flatmap, @@ -60,6 +122,37 @@ export const FlatmappableSync: Flatmappable = { wrap, }; +/** + * @since 2.0.0 + */ export const FoldableSync: Foldable = { fold }; +/** + * @since 2.0.0 + */ +export const MappableSync: Mappable = { map }; + +/** + * @since 2.0.0 + */ export const TraversableSync: Traversable = { map, fold, traverse }; + +/** + * @since 2.0.0 + */ +export const WrappableSync: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableSync); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableSync); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableSync); diff --git a/sync_either.ts b/sync_either.ts index 4933654..d6d653a 100644 --- a/sync_either.ts +++ b/sync_either.ts @@ -1,31 +1,67 @@ +/** + * This file contains the SyncEither algebraic data type. SyncEither is an + * lazy function that takes no inputs and returns an Either. The intuition of a + * SyncEither is an IO call that can fail. + * + * @module SyncEither + * @since 2.0.0 + */ + import type { Kind, Out } from "./kind.ts"; -import type { Bimappable } from "./bimappable.ts"; import type { Applicable } from "./applicable.ts"; -import type { Mappable } from "./mappable.ts"; +import type { Bimappable } from "./bimappable.ts"; +import type { Combinable } from "./combinable.ts"; +import type { Either } from "./either.ts"; import type { Failable } from "./failable.ts"; -import type { Foldable } from "./foldable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; +import type { Initializable } from "./initializable.ts"; +import type { Mappable } from "./mappable.ts"; import type { Sync } from "./sync.ts"; -import type { Either } from "./either.ts"; +import type { Wrappable } from "./wrappable.ts"; import * as E from "./either.ts"; import * as I from "./sync.ts"; import { constant, flow, pipe } from "./fn.ts"; +import { createBind, createTap } from "./flatmappable.ts"; +import { createBindTo } from "./mappable.ts"; +/** + * @since 2.0.0 + */ export type SyncEither = Sync>; +/** + * @since 2.0.0 + */ export interface KindSyncEither extends Kind { readonly kind: SyncEither, Out>; } +/** + * @since 2.0.0 + */ +export interface KindSyncRight extends Kind { + readonly kind: SyncEither>; +} + +/** + * @since 2.0.0 + */ export function left(left: B): SyncEither { return I.wrap(E.left(left)); } +/** + * @since 2.0.0 + */ export function right(right: A): SyncEither { return I.wrap(E.right(right)); } +/** + * @since 2.0.0 + */ export function tryCatch( fa: () => A, onError: (error: unknown) => B, @@ -37,42 +73,66 @@ export function tryCatch( } } +/** + * @since 2.0.0 + */ export function fromEither( ta: E.Either, ): SyncEither { return constant(ta); } +/** + * @since 2.0.0 + */ export function fromSync(ta: Sync): SyncEither { return flow(ta, E.right); } +/** + * @since 2.0.0 + */ export function wrap(a: A): SyncEither { return right(a); } +/** + * @since 2.0.0 + */ export function fail(b: B): SyncEither { return left(b); } +/** + * @since 2.0.0 + */ export function apply( ua: SyncEither, ): (ufai: SyncEither I>) => SyncEither { return (ufai) => flow(ufai, E.apply(ua())); } +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I, ): (ta: SyncEither) => SyncEither { return I.map(E.map(fai)); } +/** + * @since 2.0.0 + */ export function mapSecond( fbj: (b: B) => J, ): (ta: SyncEither) => SyncEither { return I.map(E.mapSecond(fbj)); } +/** + * @since 2.0.0 + */ export function flatmap( faui: (a: A) => SyncEither, ): (ua: SyncEither) => SyncEither { @@ -82,6 +142,9 @@ export function flatmap( }; } +/** + * @since 2.0.0 + */ export function recover( fbui: (b: B) => SyncEither, ): (ua: SyncEither) => SyncEither { @@ -91,12 +154,18 @@ export function recover( }; } +/** + * @since 2.0.0 + */ export function alt( tb: SyncEither, ): (ta: SyncEither) => SyncEither { return (ta) => flow(ta, E.match(tb, E.right)); } +/** + * @since 2.0.0 + */ export function fold( foao: (o: O, a: A) => O, o: O, @@ -104,19 +173,68 @@ export function fold( return (ta) => pipe(ta(), E.match(() => o, (a) => foao(o, a))); } -export const MappableSyncEither: Mappable = { map }; +/** + * @since 2.0.0 + */ +export function getCombinableSyncEither( + CA: Combinable, + CB: Combinable, +): Combinable> { + const { combine } = E.getCombinableEither(CA, CB); + return { + combine: (second) => (first) => () => combine(second())(first()), + }; +} -export const BimappableSyncEither: Bimappable = { - map, - mapSecond, -}; +/** + * @since 2.0.0 + */ +export function getInitializableSyncEither( + IA: Initializable, + IB: Initializable, +): Initializable> { + const { init } = E.getInitializableEither(IA, IB); + return { + init: () => init, + ...getCombinableSyncEither(IA, IB), + }; +} +/** + * @since 2.0.0 + */ +export function getFlatmappableSyncRight( + C: Combinable, +): Flatmappable> { + const right = E.getFlatmappableRight(C); + return { + wrap, + map, + flatmap, + apply: (ua) => (ufai) => () => right.apply(ua())(ufai()), + }; +} + +/** + * @since 2.0.0 + */ export const ApplicableSyncEither: Applicable = { apply, map, wrap, }; +/** + * @since 2.0.0 + */ +export const BimappableSyncEither: Bimappable = { + map, + mapSecond, +}; + +/** + * @since 2.0.0 + */ export const FlatmappableSyncEither: Flatmappable = { apply, flatmap, @@ -124,6 +242,9 @@ export const FlatmappableSyncEither: Flatmappable = { wrap, }; +/** + * @since 2.0.0 + */ export const FailableSyncEither: Failable = { alt, apply, @@ -134,4 +255,32 @@ export const FailableSyncEither: Failable = { wrap, }; +/** + * @since 2.0.0 + */ export const FoldableSyncEither: Foldable = { fold }; + +/** + * @since 2.0.0 + */ +export const MappableSyncEither: Mappable = { map }; + +/** + * @since 2.0.0 + */ +export const WrappableSyncEither: Wrappable = { wrap }; + +/** + * @since 2.0.0 + */ +export const tap = createTap(FlatmappableSyncEither); + +/** + * @since 2.0.0 + */ +export const bind = createBind(FlatmappableSyncEither); + +/** + * @since 2.0.0 + */ +export const bindTo = createBindTo(MappableSyncEither); diff --git a/testing/async_either.test.ts b/testing/async_either.test.ts index c00cced..6ddb172 100644 --- a/testing/async_either.test.ts +++ b/testing/async_either.test.ts @@ -3,6 +3,7 @@ import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as A from "../async.ts"; import * as AE from "../async_either.ts"; import * as E from "../either.ts"; +import * as N from "../number.ts"; import { pipe } from "../fn.ts"; import { then, wait } from "../promise.ts"; @@ -174,21 +175,52 @@ Deno.test("AsyncEither match", async () => { assertEquals(await fold(AE.left("asdf"))(), "asdf"); }); -// Deno.test("AsyncEither Do, bind, bindTo", () => { -// assertEqualsT( -// pipe( -// AE.Do(), -// AE.bind("one", () => AE.right(1)), -// AE.bind("two", ({ one }) => AE.right(one + one)), -// AE.map(({ one, two }) => one + two), -// ), -// AE.right(3), -// ); -// assertEqualsT( -// pipe( -// AE.right(1), -// AE.bindTo("one"), -// ), -// AE.right({ one: 1 }), -// ); -// }); +Deno.test("AsyncEither getCombinableAsyncEither", async () => { + const { combine } = AE.getCombinableAsyncEither( + N.InitializableNumberSum, + N.InitializableNumberSum, + ); + + const right = combine(AE.right(1)); + const left = combine(AE.left(1)); + + assertEquals(await right(AE.right(2))(), E.right(3)); + assertEquals(await right(AE.left(2))(), E.left(2)); + assertEquals(await left(AE.right(2))(), E.left(1)); + assertEquals(await left(AE.left(2))(), E.left(3)); +}); + +Deno.test("AsyncEither getInitializableAsyncEither", async () => { + const { init, combine } = AE.getInitializableAsyncEither( + N.InitializableNumberSum, + N.InitializableNumberSum, + ); + + const right = combine(AE.right(1)); + const left = combine(AE.left(1)); + + assertEquals(await init()(), E.right(0)); + assertEquals(await right(AE.right(2))(), E.right(3)); + assertEquals(await right(AE.left(2))(), E.left(2)); + assertEquals(await left(AE.right(2))(), E.left(1)); + assertEquals(await left(AE.left(2))(), E.left(3)); +}); + +Deno.test("AsyncEither Do, bind, bindTo", () => { + assertEqualsT( + pipe( + AE.wrap({}), + AE.bind("one", () => AE.right(1)), + AE.bind("two", ({ one }) => AE.right(one + one)), + AE.map(({ one, two }) => one + two), + ), + AE.right(3), + ); + assertEqualsT( + pipe( + AE.right(1), + AE.bindTo("one"), + ), + AE.right({ one: 1 }), + ); +}); diff --git a/testing/comparable.test.ts b/testing/comparable.test.ts index b2f9385..28dd382 100644 --- a/testing/comparable.test.ts +++ b/testing/comparable.test.ts @@ -231,10 +231,6 @@ Deno.test("Comparable premap", () => { assertEquals(compare(now)(now), true); }); -Deno.test("Comparable PremappableComparable", () => { - assertEquals(C.PremappableComparable.premap, C.premap); -}); - Deno.test("Comparable SchemableComparable", () => { assertEquals(C.SchemableComparable.unknown(), C.unknown); assertEquals(C.SchemableComparable.string(), C.string); diff --git a/testing/free.test.ts b/testing/contrib/free.test.ts similarity index 97% rename from testing/free.test.ts rename to testing/contrib/free.test.ts index 5b83c0e..19b3bb2 100644 --- a/testing/free.test.ts +++ b/testing/contrib/free.test.ts @@ -3,8 +3,8 @@ import { assertStrictEquals, } from "https://deno.land/std@0.103.0/testing/asserts.ts"; -import * as F from "../free.ts"; -import { pipe } from "../fn.ts"; +import * as F from "../../contrib/free.ts"; +import { pipe } from "../../fn.ts"; Deno.test("Free node", () => { assertEquals(F.node(1), { tag: "Node", value: 1 }); diff --git a/testing/datum.test.ts b/testing/datum.test.ts index 6526725..eb01480 100644 --- a/testing/datum.test.ts +++ b/testing/datum.test.ts @@ -3,6 +3,7 @@ import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as D from "../datum.ts"; import * as O from "../option.ts"; import * as N from "../number.ts"; +import * as E from "../either.ts"; import * as Sortable from "../sortable.ts"; import { pipe } from "../fn.ts"; @@ -37,11 +38,11 @@ Deno.test("Datum fromNullable", () => { }); Deno.test("Datum tryCatch", () => { - assertEquals(D.tryCatch(() => 1), D.replete(1)); + assertEquals(D.tryCatch(() => 1)(), D.replete(1)); assertEquals( D.tryCatch(() => { throw new Error(); - }), + })(), D.initial, ); }); @@ -299,6 +300,61 @@ Deno.test("Datum fold", () => { assertEquals(fold(D.refresh(1)), 1); }); +Deno.test("Datum exists", () => { + const exists = D.exists((n: number) => n > 0); + + assertEquals(exists(D.initial), false); + assertEquals(exists(D.pending), false); + assertEquals(exists(D.replete(0)), false); + assertEquals(exists(D.refresh(0)), false); + assertEquals(exists(D.replete(1)), true); + assertEquals(exists(D.refresh(1)), true); +}); + +Deno.test("Datum filter", () => { + const filter = D.filter((n: number) => n > 0); + + assertEquals(filter(D.initial), D.initial); + assertEquals(filter(D.pending), D.pending); + assertEquals(filter(D.replete(0)), D.initial); + assertEquals(filter(D.refresh(0)), D.pending); + assertEquals(filter(D.replete(1)), D.replete(1)); + assertEquals(filter(D.refresh(1)), D.refresh(1)); +}); + +Deno.test("Datum filterMap", () => { + const filterMap = D.filterMap(O.fromPredicate((n: number) => n > 0)); + + assertEquals(filterMap(D.initial), D.initial); + assertEquals(filterMap(D.pending), D.pending); + assertEquals(filterMap(D.replete(0)), D.initial); + assertEquals(filterMap(D.refresh(0)), D.pending); + assertEquals(filterMap(D.replete(1)), D.replete(1)); + assertEquals(filterMap(D.refresh(1)), D.refresh(1)); +}); + +Deno.test("Datum partition", () => { + const partition = D.partition((n: number) => n > 0); + + assertEquals(partition(D.initial), [D.initial, D.initial]); + assertEquals(partition(D.pending), [D.pending, D.pending]); + assertEquals(partition(D.replete(0)), [D.initial, D.replete(0)]); + assertEquals(partition(D.refresh(0)), [D.pending, D.refresh(0)]); + assertEquals(partition(D.replete(1)), [D.replete(1), D.initial]); + assertEquals(partition(D.refresh(1)), [D.refresh(1), D.pending]); +}); + +Deno.test("Datum partitionMap", () => { + const partitionMap = D.partitionMap(E.fromPredicate((n: number) => n > 0)); + + assertEquals(partitionMap(D.initial), [D.initial, D.initial]); + assertEquals(partitionMap(D.pending), [D.pending, D.pending]); + assertEquals(partitionMap(D.replete(0)), [D.initial, D.replete(0)]); + assertEquals(partitionMap(D.refresh(0)), [D.pending, D.refresh(0)]); + assertEquals(partitionMap(D.replete(1)), [D.replete(1), D.initial]); + assertEquals(partitionMap(D.refresh(1)), [D.refresh(1), D.pending]); +}); + Deno.test("Datum traverse", () => { const traverse = D.traverse(O.FlatmappableOption); const add = traverse(O.fromPredicate((n: number) => n > 0)); @@ -311,21 +367,21 @@ Deno.test("Datum traverse", () => { assertEquals(add(D.refresh(1)), O.some(D.refresh(1))); }); -// Deno.test("Datum Do, bind, bindTo", () => { -// assertEquals( -// pipe( -// D.Do(), -// D.bind("one", () => D.replete(1)), -// D.bind("two", ({ one }) => D.refresh(one + one)), -// D.map(({ one, two }) => one + two), -// ), -// D.refresh(3), -// ); -// assertEquals( -// pipe( -// D.replete(1), -// D.bindTo("one"), -// ), -// D.replete({ one: 1 }), -// ); -// }); +Deno.test("Datum Do, bind, bindTo", () => { + assertEquals( + pipe( + D.wrap({}), + D.bind("one", () => D.replete(1)), + D.bind("two", ({ one }) => D.refresh(one + one)), + D.map(({ one, two }) => one + two), + ), + D.refresh(3), + ); + assertEquals( + pipe( + D.replete(1), + D.bindTo("one"), + ), + D.replete({ one: 1 }), + ); +}); diff --git a/testing/either.test.ts b/testing/either.test.ts index 7506525..fdbfa8e 100644 --- a/testing/either.test.ts +++ b/testing/either.test.ts @@ -3,6 +3,7 @@ import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as E from "../either.ts"; import * as O from "../option.ts"; import * as N from "../number.ts"; +import * as P from "../pair.ts"; import * as Sortable from "../sortable.ts"; import { pipe } from "../fn.ts"; @@ -26,17 +27,7 @@ Deno.test("Either fromNullable", () => { }); Deno.test("Either tryCatch", () => { - assertEquals(E.tryCatch(() => 1, () => 1), E.right(1)); - assertEquals( - E.tryCatch(() => { - throw new Error(); - }, () => 1), - E.left(1), - ); -}); - -Deno.test("Either tryCatchWrap", () => { - const fn = E.tryCatchWrap((n: number) => { + const fn = E.tryCatch((n: number) => { if (n === 0) { throw new Error("0"); } @@ -85,14 +76,6 @@ Deno.test("Either swap", () => { assertEquals(E.swap(E.left(1)), E.right(1)); }); -Deno.test("Either stringifyJSON", () => { - const circular: Record = {}; - circular.circular = circular; - - assertEquals(E.stringifyJSON(null, String), E.right("null")); - assertEquals(E.stringifyJSON(circular, () => "Circular"), E.left("Circular")); -}); - Deno.test("Either isLeft", () => { assertEquals(E.isLeft(E.left(1)), true); assertEquals(E.isLeft(E.right(1)), false); @@ -104,7 +87,7 @@ Deno.test("Either isRight", () => { }); Deno.test("Either getShowable", () => { - const Showable = E.getShowable( + const Showable = E.getShowableEither( { show: (n: number) => n.toString() }, { show: (n: number) => n.toString() }, ); @@ -114,7 +97,7 @@ Deno.test("Either getShowable", () => { }); Deno.test("Either getSetoid", () => { - const Setoid = E.getComparable(N.ComparableNumber, N.ComparableNumber); + const Setoid = E.getComparableEither(N.ComparableNumber, N.ComparableNumber); const right = Setoid.compare(E.right(1)); const left = Setoid.compare(E.left(1)); @@ -130,7 +113,7 @@ Deno.test("Either getSetoid", () => { }); Deno.test("Either getSortable", () => { - const ord = E.getSortable(N.SortableNumber, N.SortableNumber); + const ord = E.getSortableEither(N.SortableNumber, N.SortableNumber); const lte = Sortable.lte(ord); const right = lte(E.right(2)); const left = lte(E.left(2)); @@ -150,39 +133,83 @@ Deno.test("Either getSortable", () => { assertEquals(left(E.left(3)), false); }); -Deno.test("Either getLeftInitializable", () => { - const Initializable = E.getLeftInitializable( +Deno.test("Either getCombinableEither", () => { + const { combine } = E.getCombinableEither( + N.InitializableNumberSum, N.InitializableNumberSum, ); - const right = Initializable.combine(E.right(1)); - const left = Initializable.combine(E.left(1)); - - assertEquals(right(E.right(1)), E.right(1)); - assertEquals(right(E.left(1)), E.right(1)); - assertEquals(left(E.right(1)), E.right(1)); - assertEquals(left(E.left(1)), E.left(2)); - assertEquals(Initializable.init(), E.left(0)); + const right = combine(E.right(1)); + const left = combine(E.left(1)); + + assertEquals(right(E.right(2)), E.right(3)); + assertEquals(right(E.left(2)), E.left(2)); + assertEquals(left(E.right(2)), E.left(1)); + assertEquals(left(E.left(2)), E.left(3)); }); -Deno.test("Either getRightInitializable", () => { - const Initializable = E.getRightInitializable( +Deno.test("Either getInitializableEither", () => { + const { init, combine } = E.getInitializableEither( + N.InitializableNumberSum, N.InitializableNumberSum, ); - const right = Initializable.combine(E.right(1)); - const left = Initializable.combine(E.left(1)); + const right = combine(E.right(1)); + const left = combine(E.left(1)); - assertEquals(right(E.right(1)), E.right(2)); - assertEquals(right(E.left(1)), E.left(1)); - assertEquals(left(E.right(1)), E.left(1)); - assertEquals(left(E.left(1)), E.left(1)); + assertEquals(right(E.right(2)), E.right(3)); + assertEquals(right(E.left(2)), E.left(2)); + assertEquals(left(E.right(2)), E.left(1)); + assertEquals(left(E.left(2)), E.left(3)); + + assertEquals(init(), E.right(0)); }); -Deno.test("Either getRightInitializable", () => { - const Initializable = E.getRightInitializable( +Deno.test("Either getFilterableEither", () => { + const { filter, filterMap, partition, partitionMap } = E.getFilterableEither( N.InitializableNumberSum, ); - assertEquals(Initializable.init(), E.right(N.InitializableNumberSum.init())); + assertEquals(pipe(E.right(1), filter((n) => n < 0)), E.left(0)); + assertEquals(pipe(E.right(1), filter((n) => n > 0)), E.right(1)); + assertEquals(pipe(E.left(1), filter((n) => n > 0)), E.left(1)); + + assertEquals( + pipe(E.right(1), filterMap(O.fromPredicate((n) => n > 0))), + E.right(1), + ); + assertEquals( + pipe(E.right(1), filterMap(O.fromPredicate((n) => n < 0))), + E.left(0), + ); + assertEquals( + pipe(E.left(1), filterMap(O.fromPredicate((n) => n > 0))), + E.left(1), + ); + + assertEquals( + pipe(E.right(1), partition((n) => n > 0)), + P.pair(E.right(1), E.left(0)), + ); + assertEquals( + pipe(E.right(1), partition((n) => n < 0)), + P.pair(E.left(0), E.right(1)), + ); + assertEquals( + pipe(E.left(1), partition((n) => n > 0)), + P.pair(E.left(1), E.left(1)), + ); + + assertEquals( + pipe(E.right(1), partitionMap(E.fromPredicate((n) => n > 0))), + P.pair(E.right(1), E.left(0)), + ); + assertEquals( + pipe(E.right(1), partitionMap(E.fromPredicate((n) => n < 0))), + P.pair(E.left(0), E.right(1)), + ); + assertEquals( + pipe(E.left(1), partitionMap(E.fromPredicate((n) => n > 0))), + P.pair(E.left(1), E.left(1)), + ); }); Deno.test("Either fold", () => { @@ -277,21 +304,21 @@ Deno.test("Either traverse", () => { ); }); -// Deno.test("Datum Do, bind, bindTo", () => { -// assertEquals( -// pipe( -// E.Do(), -// E.bind("one", () => E.right(1)), -// E.bind("two", ({ one }) => E.right(one + one)), -// E.map(({ one, two }) => one + two), -// ), -// E.right(3), -// ); -// assertEquals( -// pipe( -// E.right(1), -// E.bindTo("one"), -// ), -// E.right({ one: 1 }), -// ); -// }); +Deno.test("Datum Do, bind, bindTo", () => { + assertEquals( + pipe( + E.wrap({}), + E.bind("one", () => E.right(1)), + E.bind("two", ({ one }) => E.right(one + one)), + E.map(({ one, two }) => one + two), + ), + E.right(3), + ); + assertEquals( + pipe( + E.right(1), + E.bindTo("one"), + ), + E.right({ one: 1 }), + ); +}); diff --git a/testing/fn.test.ts b/testing/fn.test.ts index 5e72654..63252ff 100644 --- a/testing/fn.test.ts +++ b/testing/fn.test.ts @@ -52,6 +52,17 @@ Deno.test("Fn handleThrow", () => { ); }); +Deno.test("Fn tryCatch", () => { + const throws = (_: number): number => F.todo(); + const add = (n: number): number => n + 1; + + assertEquals(F.tryCatch(add, (n) => n)(1), 2); + assertEquals( + F.tryCatch(throws, (n) => n)(1), + new Error("TODO: this function has not been implemented"), + ); +}); + Deno.test("Fn memoize", () => { const obj = (n: number) => ({ n }); const memo = F.memoize(obj); diff --git a/testing/fn_either.test.ts b/testing/fn_either.test.ts index 74b7732..9a316df 100644 --- a/testing/fn_either.test.ts +++ b/testing/fn_either.test.ts @@ -56,6 +56,10 @@ Deno.test("FnEither wrap", () => { assertEquals(FE.wrap(0)(0), E.right(0)); }); +Deno.test("FnEither fail", () => { + assertEquals(FE.fail(1)(1), E.left(1)); +}); + Deno.test("FnEither apply", () => { const add = (n: number) => n + 1; assertEquals(pipe(FE.right(add), FE.apply(FE.right(1)))(0), FE.right(2)(0)); diff --git a/testing/iterable.test.ts b/testing/iterable.test.ts index 3da05af..48e2e0b 100644 --- a/testing/iterable.test.ts +++ b/testing/iterable.test.ts @@ -1,10 +1,14 @@ -import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; +import { + assertEquals, + assertStrictEquals, +} from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as I from "../iterable.ts"; import * as O from "../option.ts"; import * as E from "../either.ts"; import * as P from "../pair.ts"; import * as B from "../bimappable.ts"; +import * as N from "../number.ts"; import { pipe } from "../fn.ts"; const bimap = B.bimap(P.BimappablePair); @@ -212,3 +216,38 @@ Deno.test("Iterable repeat", () => { [0, 1, 2, 0, 1, 2, 0, 1, 2], ); }); + +Deno.test("Iterable init", () => { + assertEquals( + pipe( + I.init(), + I.collect, + ), + [], + ); +}); + +Deno.test("Iterable combine", () => { + assertEquals(pipe(I.range(2), I.combine(I.range(2, 2)), I.collect), [ + 0, + 1, + 2, + 3, + ]); +}); + +Deno.test("Iterable getCombinable", () => { + const { combine } = I.getCombinable(); + assertStrictEquals(combine, I.combine); +}); + +Deno.test("Iterable getInitializable", () => { + const { init, combine } = I.getInitializable(); + assertStrictEquals(combine, I.combine); + assertStrictEquals(init, I.init); +}); + +Deno.test("Iterable getShowable", () => { + const { show } = I.getShowable(N.ShowableNumber); + assertEquals(pipe(I.range(2), show), "Iterable[0, 1]"); +}); diff --git a/testing/map.test.ts b/testing/map.test.ts index aa73f9e..12efe64 100644 --- a/testing/map.test.ts +++ b/testing/map.test.ts @@ -249,3 +249,96 @@ Deno.test("Map getCombinable", () => { M.singleton(1, 2), ); }); + +Deno.test("Map getFlatmappable", () => { + const { apply, map, flatmap, wrap } = M.getFlatmappableReadonlyMap( + N.InitializableNumberSum, + ); + + assertEquals( + pipe( + M.init number>(), + apply(M.init()), + ), + M.init(), + ); + assertEquals( + pipe( + M.init number>(), + apply(wrap(1)), + ), + M.init(), + ); + assertEquals( + pipe( + wrap((n: number) => n + 1), + apply(M.init()), + ), + M.init(), + ); + assertEquals( + pipe( + wrap((n: number) => n + 1), + apply(M.readonlyMap([100, 1])), + ), + M.readonlyMap([100, 2]), + ); + assertEquals( + pipe( + M.readonlyMap([1, (n: number) => n + 1], [2, (n: number) => n - 1]), + apply(M.readonlyMap([100, 1], [200, 2])), + ), + M.readonlyMap([101, 2], [102, 0], [201, 3], [202, 1]), + ); + + assertEquals( + pipe( + M.init(), + map((n: number) => n + 1), + ), + M.init(), + ); + assertEquals( + pipe( + wrap(1), + map((n) => n + 1), + ), + wrap(2), + ); + assertEquals( + pipe( + M.readonlyMap([1, 1], [2, 2]), + map((n) => n + 1), + ), + M.readonlyMap([1, 2], [2, 3]), + ); + + assertEquals( + pipe( + M.init(), + flatmap((_) => M.init()), + ), + M.init(), + ); + assertEquals( + pipe( + M.init(), + flatmap((_) => wrap(1)), + ), + M.init(), + ); + assertEquals( + pipe( + wrap(1), + flatmap((n) => wrap(n + 1)), + ), + wrap(2), + ); + assertEquals( + pipe( + wrap(1), + flatmap((n) => M.readonlyMap([n, n], [n + 1, n + 1])), + ), + M.readonlyMap([1, 1], [2, 2]), + ); +}); diff --git a/testing/nil.test.ts b/testing/nil.test.ts new file mode 100644 index 0000000..103d9ba --- /dev/null +++ b/testing/nil.test.ts @@ -0,0 +1,258 @@ +import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; + +import * as N from "../nil.ts"; +import * as E from "../either.ts"; +import * as O from "../option.ts"; +import * as B from "../boolean.ts"; +import { pipe, todo } from "../fn.ts"; + +const add = (n: number) => n + 1; + +Deno.test("Nil fail", () => { + assertEquals(N.fail(), null); +}); + +Deno.test("Nil nil", () => { + assertEquals(N.nil(null), null); + assertEquals(N.nil(undefined), null); + assertEquals(N.nil(1), 1); +}); + +Deno.test("Nil init", () => { + assertEquals(N.init(), null); +}); + +Deno.test("Nil fromPredicate", () => { + const fromPredicate = N.fromPredicate((n: number) => n > 0); + + assertEquals(fromPredicate(null), null); + assertEquals(fromPredicate(undefined), null); + assertEquals(fromPredicate(0), null); + assertEquals(fromPredicate(1), 1); +}); + +Deno.test("Nil fromOption", () => { + assertEquals(N.fromOption(O.none), null); + assertEquals(N.fromOption(O.some(1)), 1); +}); + +Deno.test("Nil tryCatch", () => { + assertEquals(N.tryCatch(todo)(), null); + assertEquals(N.tryCatch((n: number) => n + 1)(1), 2); +}); + +Deno.test("Nil match", () => { + const match = N.match(() => 0, (n: number) => n); + assertEquals(match(null), 0); + assertEquals(match(undefined), 0); + assertEquals(match(1), 1); +}); + +Deno.test("Nil getOrElse", () => { + const getOrElse = N.getOrElse(() => 0); + assertEquals(getOrElse(null), 0); + assertEquals(getOrElse(undefined), 0); + assertEquals(getOrElse(1), 1); +}); + +Deno.test("Nil toNull", () => { + assertEquals(N.toNull(null), null); + assertEquals(N.toNull(undefined), null); + assertEquals(N.toNull(1), 1); +}); + +Deno.test("Nil toUndefined", () => { + assertEquals(N.toUndefined(null), undefined); + assertEquals(N.toUndefined(undefined), undefined); + assertEquals(N.toUndefined(1), 1); +}); + +Deno.test("Nil isNil", () => { + assertEquals(N.isNil(undefined), true); + assertEquals(N.isNil(null), true); + assertEquals(N.isNil(0), false); + assertEquals(N.isNil(""), false); +}); + +Deno.test("Nil isNotNil", () => { + assertEquals(N.isNotNil(undefined), false); + assertEquals(N.isNotNil(null), false); + assertEquals(N.isNotNil(0), true); + assertEquals(N.isNotNil(""), true); +}); + +Deno.test("Nil getShowable", () => { + const { show } = N.getShowableNil({ show: (n: number) => n.toString() }); + assertEquals(show(undefined), "nil"); + assertEquals(show(null), "nil"); + assertEquals(show(1), "1"); +}); + +Deno.test("Nil wrap", () => { + assertEquals(N.wrap(1), 1); +}); + +Deno.test("Nil fail", () => { + assertEquals(N.fail(), null); +}); + +Deno.test("Nil apply", () => { + const add = (n: number) => n + 1; + + assertEquals(pipe(N.wrap(add), N.apply(N.wrap(1))), N.wrap(2)); + assertEquals(pipe(N.wrap(add), N.apply(N.fail())), null); + assertEquals(pipe(N.fail(), N.apply(N.wrap(1))), null); + assertEquals(pipe(N.fail(), N.apply(N.fail())), null); +}); + +Deno.test("Nil map", () => { + const map = N.map(add); + assertEquals(map(null), null); + assertEquals(map(undefined), null); + assertEquals(map(1), 2); +}); + +Deno.test("Nil alt", () => { + assertEquals(pipe(N.wrap(1), N.alt(N.wrap(2))), N.wrap(1)); + assertEquals(pipe(N.wrap(1), N.alt(N.fail())), N.wrap(1)); + assertEquals(pipe(N.fail(), N.alt(N.wrap(1))), N.wrap(1)); + assertEquals(pipe(N.fail(), N.alt(N.fail())), N.fail()); +}); + +Deno.test("Nil exists", () => { + const exists = N.exists((n: number) => n > 0); + assertEquals(exists(null), false); + assertEquals(exists(undefined), false); + assertEquals(exists(0), false); + assertEquals(exists(1), true); +}); + +Deno.test("Nil filter", () => { + const filter = N.filter((n: number) => n > 0); + assertEquals(filter(null), null); + assertEquals(filter(undefined), null); + assertEquals(filter(0), null); + assertEquals(filter(1), 1); +}); + +Deno.test("Nil filterMap", () => { + const filterMap = N.filterMap(O.fromPredicate((n: number) => n > 0)); + assertEquals(filterMap(null), null); + assertEquals(filterMap(undefined), null); + assertEquals(filterMap(0), null); + assertEquals(filterMap(1), 1); +}); + +Deno.test("Nil partition", () => { + const partition = N.partition((n: number) => n > 0); + assertEquals(partition(null), [null, null]); + assertEquals(partition(undefined), [null, null]); + assertEquals(partition(0), [null, 0]); + assertEquals(partition(1), [1, null]); +}); + +Deno.test("Nil partitionMap", () => { + const partition = N.partitionMap(E.fromPredicate((n: number) => n > 0)); + assertEquals(partition(null), [null, null]); + assertEquals(partition(undefined), [null, null]); + assertEquals(partition(0), [null, 0]); + assertEquals(partition(1), [1, null]); +}); + +Deno.test("Nil traverse", () => { + const traverse = N.traverse(O.ApplicableOption)( + O.fromPredicate((n: number) => n > 0), + ); + assertEquals(traverse(null), O.some(null)); + assertEquals(traverse(undefined), O.some(null)); + assertEquals(traverse(0), O.none); + assertEquals(traverse(1), O.some(1)); +}); + +Deno.test("Nil fold", () => { + const fold = N.fold((n: number, m: number) => n + m, 1); + assertEquals(fold(null), 1); + assertEquals(fold(undefined), 1); + assertEquals(fold(1), 2); +}); + +Deno.test("Nil getComparableNil", () => { + const { compare } = N.getComparableNil(B.ComparableBoolean); + assertEquals(compare(null)(null), true); + assertEquals(compare(null)(undefined), true); + assertEquals(compare(undefined)(undefined), true); + assertEquals(compare(undefined)(null), true); + assertEquals(compare(true)(false), false); + assertEquals(compare(true)(true), true); + assertEquals(compare(false)(true), false); + assertEquals(compare(false)(false), true); +}); + +Deno.test("Nil getSortableNil", () => { + const { sort } = N.getSortableNil(B.SortableBoolean); + assertEquals(sort(null, null), 0); + assertEquals(sort(null, undefined), 0); + assertEquals(sort(undefined, null), 0); + assertEquals(sort(undefined, undefined), 0); + assertEquals(sort(null, true), -1); + assertEquals(sort(true, null), 1); + assertEquals(sort(true, true), 0); + assertEquals(sort(true, false), 1); + assertEquals(sort(false, true), -1); + assertEquals(sort(false, false), 0); +}); + +Deno.test("Nil getCombinableNil", () => { + const { combine } = N.getCombinableNil(B.CombinableBooleanAll); + assertEquals(combine(null)(null), null); + assertEquals(combine(null)(undefined), null); + assertEquals(combine(undefined)(null), null); + assertEquals(combine(null)(undefined), null); + assertEquals(combine(null)(true), true); + assertEquals(combine(true)(null), true); + assertEquals(combine(true)(true), true); + assertEquals(combine(true)(false), false); + assertEquals(combine(false)(true), false); + assertEquals(combine(false)(false), false); +}); + +Deno.test("Nil getInitializableNil", () => { + const { init, combine } = N.getInitializableNil(B.InitializableBooleanAll); + assertEquals(init(), true); + assertEquals(combine(null)(null), null); + assertEquals(combine(null)(undefined), null); + assertEquals(combine(undefined)(null), null); + assertEquals(combine(null)(undefined), null); + assertEquals(combine(null)(true), true); + assertEquals(combine(true)(null), true); + assertEquals(combine(true)(true), true); + assertEquals(combine(true)(false), false); + assertEquals(combine(false)(true), false); + assertEquals(combine(false)(false), false); +}); + +Deno.test("Nil flatmap", () => { + const flatmap = N.flatmap((n: number) => n === 0 ? null : n); + assertEquals(flatmap(undefined), null); + assertEquals(flatmap(null), null); + assertEquals(flatmap(1), 1); +}); + +Deno.test("Nil Do, bind, bindTo", () => { + assertEquals( + pipe( + {}, + N.bind("one", () => 1), + N.bind("two", ({ one }) => one + one), + N.map(({ one, two }) => one + two), + ), + 3, + ); + assertEquals( + pipe( + N.nil(1), + N.bindTo("one"), + ), + N.nil({ one: 1 }), + ); +}); diff --git a/testing/nilable.test.ts b/testing/nilable.test.ts deleted file mode 100644 index 829d3fe..0000000 --- a/testing/nilable.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; - -import * as N from "../nilable.ts"; -import { pipe, todo } from "../fn.ts"; - -const add = (n: number) => n + 1; - -Deno.test("Nilable nil", () => { - assertEquals(N.nil, undefined); -}); - -Deno.test("Nilable constNil", () => { - assertEquals(N.constNil(), N.nil); -}); - -Deno.test("Nilable make", () => { - assertEquals(N.make(null), N.nil); - assertEquals(N.make(undefined), N.nil); - assertEquals(N.make(1), 1); -}); - -Deno.test("Nilable fromPredicate", () => { - const fromPredicate = N.fromPredicate((n: number) => n > 0); - - assertEquals(fromPredicate(null), N.nil); - assertEquals(fromPredicate(undefined), N.nil); - assertEquals(fromPredicate(0), N.nil); - assertEquals(fromPredicate(1), 1); -}); - -Deno.test("Nilable tryCatch", () => { - assertEquals(N.tryCatch(todo), N.nil); - assertEquals(N.tryCatch(() => 1), 1); -}); - -Deno.test("Nilable match", () => { - const match = N.match(() => 0, (n: number) => n); - assertEquals(match(null), 0); - assertEquals(match(undefined), 0); - assertEquals(match(1), 1); -}); - -Deno.test("Nilable getOrElse", () => { - const getOrElse = N.getOrElse(() => 0); - assertEquals(getOrElse(null), 0); - assertEquals(getOrElse(undefined), 0); - assertEquals(getOrElse(1), 1); -}); - -Deno.test("Nilable toNull", () => { - assertEquals(N.toNull(null), null); - assertEquals(N.toNull(undefined), null); - assertEquals(N.toNull(1), 1); -}); - -Deno.test("Nilable toUndefined", () => { - assertEquals(N.toUndefined(null), undefined); - assertEquals(N.toUndefined(undefined), undefined); - assertEquals(N.toUndefined(1), 1); -}); - -Deno.test("Nilable isNil", () => { - assertEquals(N.isNil(undefined), true); - assertEquals(N.isNil(null), true); - assertEquals(N.isNil(0), false); - assertEquals(N.isNil(""), false); -}); - -Deno.test("Nilable isNotNil", () => { - assertEquals(N.isNotNil(undefined), false); - assertEquals(N.isNotNil(null), false); - assertEquals(N.isNotNil(0), true); - assertEquals(N.isNotNil(""), true); -}); - -Deno.test("Nilable getShowable", () => { - const { show } = N.getShowable({ show: (n: number) => n.toString() }); - assertEquals(show(undefined), "nil"); - assertEquals(show(null), "nil"); - assertEquals(show(1), "1"); -}); - -Deno.test("Nilable wrap", () => { - assertEquals(N.wrap(1), 1); -}); - -Deno.test("Nilable fail", () => { - assertEquals(N.fail(), N.nil); -}); - -Deno.test("Nilable apply", () => { - const add = (n: number) => n + 1; - - assertEquals(pipe(N.wrap(add), N.apply(N.wrap(1))), N.wrap(2)); - assertEquals(pipe(N.wrap(add), N.apply(N.constNil())), N.nil); - assertEquals(pipe(N.constNil(), N.apply(N.wrap(1))), N.nil); - assertEquals(pipe(N.constNil(), N.apply(N.constNil())), N.nil); -}); - -Deno.test("Nilable map", () => { - const map = N.map(add); - assertEquals(map(null), N.nil); - assertEquals(map(undefined), N.nil); - assertEquals(map(1), 2); -}); - -Deno.test("Nilable alt", () => { - assertEquals(pipe(N.wrap(1), N.alt(N.wrap(2))), N.wrap(1)); - assertEquals(pipe(N.wrap(1), N.alt(N.fail())), N.wrap(1)); - assertEquals(pipe(N.fail(), N.alt(N.wrap(1))), N.wrap(1)); - assertEquals(pipe(N.fail(), N.alt(N.fail())), N.fail()); -}); - -Deno.test("Nilable flatmap", () => { - const flatmap = N.flatmap((n: number) => n === 0 ? N.nil : n); - assertEquals(flatmap(undefined), N.nil); - assertEquals(flatmap(null), N.nil); - assertEquals(flatmap(1), 1); -}); - -// Deno.test("Nilable Do, bind, bindTo", () => { -// assertEquals( -// pipe( -// N.Do(), -// N.bind("one", () => N.make(1)), -// N.bind("two", ({ one }) => N.make(one + one)), -// N.map(({ one, two }) => one + two), -// ), -// N.make(3), -// ); -// assertEquals( -// pipe( -// N.make(1), -// N.bindTo("one"), -// ), -// N.make({ one: 1 }), -// ); -// }); diff --git a/testing/optic.test.ts b/testing/optic.test.ts index 6b35fac..c278afa 100644 --- a/testing/optic.test.ts +++ b/testing/optic.test.ts @@ -304,44 +304,6 @@ Deno.test("Optic imap", () => { assertEquals(optic.view(1), 101); }); -Deno.test("Optic map", () => { - const optic = pipe(O.id(), O.map((n) => n + 1)); - assertEquals(optic.view(1), 2); -}); - -Deno.test("Optic ap", () => { - type Person = { name: string; age: number }; - type State = { people: readonly Person[]; format: (p: Person) => string }; - - const fmt = pipe(O.id(), O.prop("format")); - const adults = pipe( - O.id(), - O.prop("people"), - O.array, - O.filter((p) => p.age > 18), - ); - - const formatted = pipe(fmt, O.apply(adults)); - - const fmt1 = (p: Person) => `${p.name} is ${p.age}`; - const fmt2 = (p: Person) => `At ${p.age} we have ${p.name}`; - const people1: readonly Person[] = []; - const people2: readonly Person[] = [ - { name: "Brandon", age: 37 }, - { name: "Rufus", age: 1 }, - ]; - - const state1: State = { format: fmt1, people: people1 }; - const state2: State = { format: fmt1, people: people2 }; - const state3: State = { format: fmt2, people: people1 }; - const state4: State = { format: fmt2, people: people2 }; - - assertEquals(formatted.view(state1), []); - assertEquals(formatted.view(state2), ["Brandon is 37"]); - assertEquals(formatted.view(state3), []); - assertEquals(formatted.view(state4), ["At 37 we have Brandon"]); -}); - Deno.test("Optic prop", () => { type Foo = { one: number; two: string }; const one = pipe(O.id(), O.prop("one")); @@ -551,8 +513,8 @@ Deno.test("Optic tree", () => { assertEquals(incr(T.tree(1, [T.tree(2)])), T.tree(2, [T.tree(3)])); }); -Deno.test("Optic nilable", () => { - const optic = pipe(O.id(), O.nilable); +Deno.test("Optic nil", () => { + const optic = pipe(O.id(), O.nil); const incr = pipe(optic, O.modify(inc)); assertEquals(optic.view(undefined), Op.none); diff --git a/testing/option.test.ts b/testing/option.test.ts index c023c57..e05679d 100644 --- a/testing/option.test.ts +++ b/testing/option.test.ts @@ -173,13 +173,13 @@ Deno.test("Option exists", () => { }); Deno.test("Option getShowable", () => { - const { show } = O.getShowable({ show: (n: number) => n.toString() }); + const { show } = O.getShowableOption({ show: (n: number) => n.toString() }); assertEquals(show(O.none), "None"); assertEquals(show(O.some(1)), "Some(1)"); }); Deno.test("Option getComparable", () => { - const { compare } = O.getComparable(N.ComparableNumber); + const { compare } = O.getComparableOption(N.ComparableNumber); assertEquals(compare(O.some(1))(O.some(1)), true); assertEquals(compare(O.some(1))(O.some(2)), false); assertEquals(compare(O.some(1))(O.none), false); @@ -188,7 +188,7 @@ Deno.test("Option getComparable", () => { }); Deno.test("Option getSortable", () => { - const ord = O.getSortable(N.SortableNumber); + const ord = O.getSortableOption(N.SortableNumber); const lte = Sortable.lte(ord); assertEquals(lte(O.none)(O.none), true); assertEquals(lte(O.none)(O.some(1)), false); @@ -199,29 +199,29 @@ Deno.test("Option getSortable", () => { }); Deno.test("Option getInitializable", () => { - const { combine, init } = O.getInitializable(N.InitializableNumberSum); + const { combine, init } = O.getInitializableOption(N.InitializableNumberSum); assertEquals(combine(O.some(1))(O.some(1)), O.some(2)); assertEquals(combine(O.some(1))(O.none), O.some(1)); assertEquals(combine(O.none)(O.some(1)), O.some(1)); assertEquals(combine(O.none)(O.none), O.none); - assertEquals(init(), O.none); -}); - -// Deno.test("Option Do, bind, bindTo", () => { -// assertEquals( -// pipe( -// O.Do(), -// O.bind("one", () => O.some(1)), -// O.bind("two", ({ one }) => O.some(one + one)), -// O.map(({ one, two }) => one + two), -// ), -// O.some(3), -// ); -// assertEquals( -// pipe( -// O.some(1), -// O.bindTo("one"), -// ), -// O.some({ one: 1 }), -// ); -// }); + assertEquals(init(), O.some(0)); +}); + +Deno.test("Option Do, bind, bindTo", () => { + assertEquals( + pipe( + O.some({}), + O.bind("one", () => O.some(1)), + O.bind("two", ({ one }) => O.some(one + one)), + O.map(({ one, two }) => one + two), + ), + O.some(3), + ); + assertEquals( + pipe( + O.some(1), + O.bindTo("one"), + ), + O.some({ one: 1 }), + ); +}); diff --git a/testing/pair.test.ts b/testing/pair.test.ts index 1342211..aba4217 100644 --- a/testing/pair.test.ts +++ b/testing/pair.test.ts @@ -4,7 +4,8 @@ import { } from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as P from "../pair.ts"; -import { InitializableNumberSum, ShowableNumber } from "../number.ts"; +import * as N from "../number.ts"; +import * as C from "../comparable.ts"; import { pipe } from "../fn.ts"; Deno.test("Pair pair", () => { @@ -79,7 +80,7 @@ Deno.test("Pair fold", () => { }); Deno.test("Pair traverse", () => { - const M = P.getRightFlatmappable(InitializableNumberSum); + const M = P.getRightFlatmappable(N.InitializableNumberSum); const traversePair = P.traverse(M); assertEquals( pipe( @@ -112,10 +113,10 @@ Deno.test("Pair TraversablePair", () => { Deno.test("Pair getRightFlatmappable", () => { // TODO Work on generic Flatmappable tests const { apply, flatmap, map, wrap } = P.getRightFlatmappable( - InitializableNumberSum, + N.InitializableNumberSum, ); - assertEquals(wrap(1), [1, InitializableNumberSum.init()]); + assertEquals(wrap(1), [1, N.InitializableNumberSum.init()]); assertEquals( pipe( P.pair((s: string) => s.toUpperCase(), 5), @@ -139,7 +140,52 @@ Deno.test("Pair getRightFlatmappable", () => { ); }); -Deno.test("Pair getShowable", () => { - const { show } = P.getShowable(ShowableNumber, ShowableNumber); +Deno.test("Pair getShowablePair", () => { + const { show } = P.getShowablePair(N.ShowableNumber, N.ShowableNumber); assertEquals(show(P.pair(1, 2)), "Pair(1, 2)"); }); + +Deno.test("Pair getCombinablePair", () => { + const { combine } = P.getCombinablePair( + N.CombinableNumberMax, + N.CombinableNumberMin, + ); + assertEquals(combine(P.pair(1, 1))(P.pair(2, 2)), P.pair(2, 1)); + assertEquals(combine(P.pair(2, 2))(P.pair(1, 1)), P.pair(2, 1)); +}); + +Deno.test("Pair getInitializablePair", () => { + const { init, combine } = P.getInitializablePair( + N.InitializableNumberMax, + N.InitializableNumberMin, + ); + assertEquals( + init(), + P.pair(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY), + ); + assertEquals(combine(P.pair(1, 1))(P.pair(2, 2)), P.pair(2, 1)); + assertEquals(combine(P.pair(2, 2))(P.pair(1, 1)), P.pair(2, 1)); +}); + +Deno.test("Pair getComparablePair", () => { + const { compare } = P.getComparablePair( + N.ComparableNumber, + N.ComparableNumber, + ); + assertEquals(compare(P.pair(1, 1))(P.pair(1, 1)), true); + assertEquals(compare(P.pair(1, 1))(P.pair(2, 2)), false); + assertEquals(compare(P.pair(2, 2))(P.pair(1, 1)), false); + assertEquals(compare(P.pair(1, 1))(P.pair(1, 2)), false); + assertEquals(compare(P.pair(1, 2))(P.pair(1, 1)), false); + assertEquals(compare(P.pair(2, 1))(P.pair(1, 1)), false); + assertEquals(compare(P.pair(1, 1))(P.pair(2, 1)), false); +}); + +Deno.test("Pair getSortablePair", () => { + const { sort } = P.getSortablePair(N.SortableNumber, N.SortableNumber); + assertEquals(sort(P.pair(0, 0), P.pair(0, 0)), 0); + assertEquals(sort(P.pair(0, 0), P.pair(0, 1)), -1); + assertEquals(sort(P.pair(0, 1), P.pair(0, 0)), 1); + assertEquals(sort(P.pair(0, 0), P.pair(1, 0)), -1); + assertEquals(sort(P.pair(1, 0), P.pair(0, 0)), 1); +}); diff --git a/testing/predicate.test.ts b/testing/predicate.test.ts index 398befa..b60bada 100644 --- a/testing/predicate.test.ts +++ b/testing/predicate.test.ts @@ -56,16 +56,12 @@ Deno.test("Predicate and", () => { assertEquals(isPositiveInteger(-2.2), false); }); -Deno.test("Predicate PremappablePredicate", () => { - assertStrictEquals(P.PremappablePredicate.premap, P.premap); +Deno.test("Predicate getCombinableAny", () => { + assertStrictEquals(P.getCombinableAny().combine, P.or); }); -Deno.test("Predicate getInitializableAny", () => { - assertStrictEquals(P.getInitializableAny().combine, P.or); -}); - -Deno.test("Predicate getInitializableAll", () => { - assertStrictEquals(P.getInitializableAll().combine, P.and); +Deno.test("Predicate getCombinableAll", () => { + assertStrictEquals(P.getCombinableAll().combine, P.and); }); Deno.test("Predicate getInitializableAny", () => { diff --git a/testing/premappable.test.ts b/testing/premappable.test.ts index 7eb55fb..532c4f3 100644 --- a/testing/premappable.test.ts +++ b/testing/premappable.test.ts @@ -2,12 +2,3 @@ import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as P from "../premappable.ts"; import * as F from "../fn.ts"; - -Deno.test("Premappable dimap", () => { - const dimap = P.dimap({ ...F.PremappableFn, ...F.MappableFn }); - const fn = F.pipe( - F.id(), - dimap((s: string) => s.length, (n) => "nyan".repeat(n)), - ); - assertEquals(fn("Hi"), "nyannyan"); -}); diff --git a/testing/promise.test.ts b/testing/promise.test.ts index 738a247..c2a8de1 100644 --- a/testing/promise.test.ts +++ b/testing/promise.test.ts @@ -5,6 +5,7 @@ import { import * as P from "../promise.ts"; import * as E from "../either.ts"; +import * as N from "../number.ts"; import { pipe, todo } from "../fn.ts"; Deno.test("Promise deferred", async () => { @@ -142,6 +143,10 @@ Deno.test("Promise flatmap", async () => { assertEquals(await pipe(P.wrap(1), P.flatmap((n) => P.wrap(n + 1))), 2); }); +Deno.test("Promise fail", async () => { + assertEquals(await pipe(P.fail(1), P.catchError(P.wrap)), await P.wrap(1)); +}); + Deno.test("Promise tryCatch", async () => { const add = (n: number) => n + 1; const throwSync = (_: number): number => todo(); @@ -156,6 +161,17 @@ Deno.test("Promise tryCatch", async () => { assertEquals(await catchAsync(1), -1); }); +Deno.test("Promise getCombinablePromise", async () => { + const { combine } = P.getCombinablePromise(N.CombinableNumberSum); + assertEquals(await combine(P.wrap(1))(P.wrap(2)), await P.wrap(3)); +}); + +Deno.test("Promise getInitializablePromise", async () => { + const { combine, init } = P.getInitializablePromise(N.InitializableNumberSum); + assertEquals(await init(), await P.wrap(0)); + assertEquals(await combine(P.wrap(1))(P.wrap(2)), await P.wrap(3)); +}); + Deno.test("Promise MappablePromise", () => { assertStrictEquals(P.MappablePromise.map, P.map); }); diff --git a/testing/record.test.ts b/testing/record.test.ts index 78fedd7..daacfa6 100644 --- a/testing/record.test.ts +++ b/testing/record.test.ts @@ -24,7 +24,6 @@ const value3: ReadonlyRecord = { const value4: ReadonlyRecord = { one: "one", two: "two" }; const greaterThanZero: Predicate = (n: number) => n > 0; -const isPositive: Predicate = (n: number) => n >= 0; Deno.test("Record entries", () => { assertEquals(pipe(value1, R.entries), Object.entries(value1)); @@ -250,6 +249,55 @@ Deno.test("Record partitionMap", () => { assertEquals(partitionMap(value1), P.pair({ two: 2, three: 3 }, { one: 1 })); }); +Deno.test("Record getShowableRecord", () => { + const { show } = R.getShowableRecord({ + show: (n: number) => n.toString(), + }); + assertEquals(show({}), "{}"); + assertEquals(show({ a: 1, b: 2 }), "{a: 1, b: 2}"); +}); + +Deno.test("Record getCombinableRecord", () => { + const { combine } = R.getCombinableRecord(N.CombinableNumberSum); + assertEquals(combine({})({}), {}); + assertEquals(combine({ one: 1 })({}), { one: 1 }); + assertEquals(combine({})({ one: 1 }), { one: 1 }); + assertEquals(combine({ one: 1 })({ two: 1 }), { one: 1, two: 1 }); + assertEquals(combine({ one: 1 })({ one: 1 }), { one: 2 }); + assertEquals(combine({ one: 1, two: 1 })({ one: 1 }), { one: 2, two: 1 }); + assertEquals(combine({ one: 1 })({ one: 1, two: 1 }), { one: 2, two: 1 }); + assertEquals(combine({ one: 1, two: 1 })({ one: 1, two: 1 }), { + one: 2, + two: 2, + }); +}); + +Deno.test("Record getInitializableRecord", () => { + const { combine, init } = R.getInitializableRecord(N.InitializableNumberSum); + assertEquals(init(), {}); + assertEquals(combine({})({}), {}); + assertEquals(combine({ one: 1 })({}), { one: 1 }); + assertEquals(combine({})({ one: 1 }), { one: 1 }); + assertEquals(combine({ one: 1 })({ two: 1 }), { one: 1, two: 1 }); + assertEquals(combine({ one: 1 })({ one: 1 }), { one: 2 }); + assertEquals(combine({ one: 1, two: 1 })({ one: 1 }), { one: 2, two: 1 }); + assertEquals(combine({ one: 1 })({ one: 1, two: 1 }), { one: 2, two: 1 }); + assertEquals(combine({ one: 1, two: 1 })({ one: 1, two: 1 }), { + one: 2, + two: 2, + }); +}); + +Deno.test("Record getComparableRecord", () => { + const { compare } = R.getComparableRecord(N.ComparableNumber); + assertEquals(compare({})({}), true); + assertEquals(compare({ one: 1 })({}), false); + assertEquals(compare({})({ one: 1 }), false); + assertEquals(compare({ one: 1 })({ one: 1 }), true); + assertEquals(compare({ one: 1 })({ one: 2 }), false); + assertEquals(compare({ one: 2 })({ one: 1 }), false); +}); + Deno.test("Record FilterableRecord", () => { assertStrictEquals(R.FilterableRecord.filter, R.filter); assertStrictEquals(R.FilterableRecord.filterMap, R.filterMap); @@ -266,9 +314,3 @@ Deno.test("Record TraversableRecord", () => { assertStrictEquals(R.TraversableRecord.fold, R.fold); assertStrictEquals(R.TraversableRecord.traverse, R.traverse); }); - -Deno.test("Record getShowable", () => { - const { show } = R.getShowable({ show: (n: number) => n.toString() }); - assertEquals(show({}), "{}"); - assertEquals(show({ a: 1, b: 2 }), "{a: 1, b: 2}"); -}); diff --git a/testing/reducible.test.ts b/testing/reducible.test.ts deleted file mode 100644 index e831f1d..0000000 --- a/testing/reducible.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; - -import * as R from "../reducible.ts"; -import * as A from "../array.ts"; -import * as N from "../number.ts"; - -Deno.test("Reducible collect", () => { - const collect = R.collect(A.ReducibleArray, N.InitializableNumberSum); - assertEquals(collect([]), 0); - assertEquals(collect([1, 2, 3, 4]), 10); -}); diff --git a/testing/set.test.ts b/testing/set.test.ts index b326e7f..28eab5f 100644 --- a/testing/set.test.ts +++ b/testing/set.test.ts @@ -174,21 +174,21 @@ Deno.test("Set TraversableSet", () => { assertStrictEquals(S.TraversableSet.traverse, S.traverse); }); -Deno.test("Set getShowable", () => { - const { show } = S.getShowable({ show: (n: number) => n.toString() }); +Deno.test("Set getShowableSet", () => { + const { show } = S.getShowableSet({ show: (n: number) => n.toString() }); assertEquals(show(S.init()), "Set([])"); assertEquals(show(S.set(1, 2, 3)), "Set([1, 2, 3])"); }); -Deno.test("Set getComparable", () => { - const eq = S.getComparable(ComparableNumber); +Deno.test("Set getComparableSet", () => { + const eq = S.getComparableSet(ComparableNumber); assertEquals(pipe(S.init(), eq.compare(S.init())), true); assertEquals(pipe(set, eq.compare(S.init())), false); assertEquals(pipe(set, eq.compare(S.set(3, 2, 2, 1))), true); }); -Deno.test("Set getUnionCombinable", () => { - const monoid = S.getUnionCombinable(ComparableNumber); +Deno.test("Set getCombinableSet", () => { + const monoid = S.getCombinableSet(ComparableNumber); const combineAll = M.getCombineAll(monoid); assertEquals(combineAll(S.init()), S.init()); @@ -201,3 +201,19 @@ Deno.test("Set getUnionCombinable", () => { S.set(1, 2, 3, 4, 5, 6, 7), ); }); + +Deno.test("Set getInitializableSet", () => { + const monoid = S.getInitializableSet(ComparableNumber); + const combineAll = M.getCombineAll(monoid); + + assertEquals(monoid.init(), S.init()); + assertEquals(combineAll(S.init()), S.init()); + assertEquals( + combineAll( + S.set(1, 2, 3), + S.set(4, 5, 6), + S.set(1, 3, 5, 7), + ), + S.set(1, 2, 3, 4, 5, 6, 7), + ); +}); diff --git a/testing/sortable.test.ts b/testing/sortable.test.ts index 12dfe81..e54b9a6 100644 --- a/testing/sortable.test.ts +++ b/testing/sortable.test.ts @@ -175,7 +175,3 @@ Deno.test("Sortable premap", () => { -1, ); }); - -Deno.test("Sortable PremappableSortable", () => { - assertStrictEquals(O.PremappableSortable.premap, O.premap); -}); diff --git a/testing/state.test.ts b/testing/state.test.ts index 74a10fe..3e80f79 100644 --- a/testing/state.test.ts +++ b/testing/state.test.ts @@ -1,6 +1,8 @@ import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; import * as S from "../state.ts"; +import * as N from "../number.ts"; +import * as C from "../combinable.ts"; import { pipe } from "../fn.ts"; const add = (n: number) => n + 1; @@ -26,7 +28,7 @@ Deno.test("State gets", () => { assertEquals(S.gets((n: number) => n.toString())(0), ["0", 0]); }); -Deno.test("State make", () => { +Deno.test("State state", () => { assertEqualsS(S.state(1, 1), S.id()); }); @@ -56,21 +58,44 @@ Deno.test("State execute", () => { assertEquals(pipe(S.id(), S.execute(0)), 0); }); -// Deno.test("State Do, bind, bindTo", () => { -// assertEqualsS( -// pipe( -// S.Do(), -// S.bind("one", () => S.make(1, 1)), -// S.bind("two", ({ one }) => S.make(one + one, 1)), -// S.map(({ one, two }) => one + two), -// ), -// S.make(3, 1), -// ); -// assertEqualsS( -// pipe( -// S.make(1, 1), -// S.bindTo("one"), -// ), -// S.make({ one: 1 }, 1), -// ); -// }); +Deno.test("State getCombinableState", () => { + const { combine } = S.getCombinableState( + N.CombinableNumberMax, + N.CombinableNumberMin, + ); + assertEquals( + combine((n: number) => [n, n + 1])((n: number) => [n + 100, n])(1), + [1, 2], + ); +}); + +Deno.test("State getInitializableState", () => { + const { combine, init } = S.getInitializableState( + N.InitializableNumberSum, + N.InitializableNumberProduct, + ); + assertEquals(init()(1), [1, 1]); + assertEquals( + combine((n: number) => [n, n])((n: number) => [n, n])(2), + [4, 4], + ); +}); + +Deno.test("State Do, bind, bindTo", () => { + assertEquals( + pipe( + S.wrap<{}, number>({}), + S.bind("one", () => S.state(1, 1)), + S.bind("two", ({ one }) => S.state(one + one, 1)), + S.map(({ one, two }) => one + two), + )(1), + S.state(3, 1)(1), + ); + assertEquals( + pipe( + S.state(1, 1), + S.bindTo("one"), + )(1), + S.state({ one: 1 }, 1)(1), + ); +}); diff --git a/testing/sync.test.ts b/testing/sync.test.ts index a0a51d6..af95c5d 100644 --- a/testing/sync.test.ts +++ b/testing/sync.test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "https://deno.land/std@0.103.0/testing/asserts.ts"; import * as S from "../sync.ts"; import * as O from "../option.ts"; +import * as N from "../number.ts"; import { pipe } from "../fn.ts"; const add = (n: number) => n + 1; @@ -44,21 +45,32 @@ Deno.test("Sync traverse", () => { assertEquals(t3, 1); }); -// Deno.test("Sync Do, bind, bindTo", () => { -// assertEqualsSync( -// pipe( -// S.Do(), -// S.bind("one", () => S.wrap(1)), -// S.bind("two", ({ one }) => S.wrap(one + one)), -// S.map(({ one, two }) => one + two), -// ), -// S.wrap(3), -// ); -// assertEqualsSync( -// pipe( -// S.wrap(1), -// S.bindTo("one"), -// ), -// S.wrap({ one: 1 }), -// ); -// }); +Deno.test("Sync getCombinableSync", () => { + const { combine } = S.getCombinableSync(N.CombinableNumberSum); + assertEquals(combine(S.wrap(1))(S.wrap(1))(), 2); +}); + +Deno.test("Sync getInitializableSync", () => { + const { combine, init } = S.getInitializableSync(N.InitializableNumberSum); + assertEquals(init()(), 0); + assertEquals(combine(S.wrap(1))(S.wrap(1))(), 2); +}); + +Deno.test("Sync Do, bind, bindTo", () => { + assertEqualsSync( + pipe( + S.wrap({}), + S.bind("one", () => S.wrap(1)), + S.bind("two", ({ one }) => S.wrap(one + one)), + S.map(({ one, two }) => one + two), + ), + S.wrap(3), + ); + assertEqualsSync( + pipe( + S.wrap(1), + S.bindTo("one"), + ), + S.wrap({ one: 1 }), + ); +}); diff --git a/testing/sync_either.test.ts b/testing/sync_either.test.ts index fd5737d..71b665a 100644 --- a/testing/sync_either.test.ts +++ b/testing/sync_either.test.ts @@ -1,8 +1,12 @@ -import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; +import { + assertEquals, + assertStrictEquals, +} from "https://deno.land/std/testing/asserts.ts"; import * as SE from "../sync_either.ts"; import * as S from "../sync.ts"; import * as E from "../either.ts"; +import * as N from "../number.ts"; import { constant, pipe, todo } from "../fn.ts"; const assertEqualsIO = ( @@ -99,21 +103,60 @@ Deno.test("SyncEither recover", () => { assertEqualsIO(recover(SE.left(1)), SE.right(2)); }); -// Deno.test("Datum Do, bind, bindTo", () => { -// assertEqualsIO( -// pipe( -// SE.Do(), -// SE.bind("one", () => SE.right(1)), -// SE.bind("two", ({ one }) => SE.right(one + one)), -// SE.map(({ one, two }) => one + two), -// ), -// SE.right(3), -// ); -// assertEqualsIO( -// pipe( -// SE.right(1), -// SE.bindTo("one"), -// ), -// SE.right({ one: 1 }), -// ); -// }); +Deno.test("SyncEither getCombinableSyncEither", () => { + const { combine } = SE.getCombinableSyncEither( + N.InitializableNumberSum, + N.InitializableNumberSum, + ); + assertEquals(combine(SE.wrap(1))(SE.wrap(2))(), E.right(3)); + assertEquals(combine(SE.wrap(1))(SE.fail(2))(), E.left(2)); + assertEquals(combine(SE.fail(1))(SE.wrap(2))(), E.left(1)); + assertEquals(combine(SE.fail(1))(SE.fail(2))(), E.left(3)); +}); + +Deno.test("SyncEither getInitializableSyncEither", () => { + const { init, combine } = SE.getInitializableSyncEither( + N.InitializableNumberSum, + N.InitializableNumberSum, + ); + assertEquals(init()(), E.right(0)); + assertEquals(combine(SE.wrap(1))(SE.wrap(2))(), E.right(3)); + assertEquals(combine(SE.wrap(1))(SE.fail(2))(), E.left(2)); + assertEquals(combine(SE.fail(1))(SE.wrap(2))(), E.left(1)); + assertEquals(combine(SE.fail(1))(SE.fail(2))(), E.left(3)); +}); + +Deno.test("SyncEither getFlatmappableSyncRight", () => { + const { apply, map, flatmap, wrap } = SE.getFlatmappableSyncRight( + N.InitializableNumberSum, + ); + const add = (n: number) => n + 1; + + assertEquals(pipe(wrap(add), apply(wrap(2)))(), E.right(3)); + assertEquals(pipe(wrap(add), apply(SE.fail(2)))(), E.left(2)); + assertEquals(pipe(SE.fail(1), apply(wrap(2)))(), E.left(1)); + assertEquals(pipe(SE.fail(1), apply(SE.fail(2)))(), E.left(3)); + + assertStrictEquals(map, SE.map); + assertStrictEquals(flatmap, SE.flatmap); + assertStrictEquals(wrap, SE.wrap); +}); + +Deno.test("Datum Do, bind, bindTo", () => { + assertEqualsIO( + pipe( + SE.wrap({}), + SE.bind("one", () => SE.right(1)), + SE.bind("two", ({ one }) => SE.right(one + one)), + SE.map(({ one, two }) => one + two), + ), + SE.right(3), + ); + assertEqualsIO( + pipe( + SE.right(1), + SE.bindTo("one"), + ), + SE.right({ one: 1 }), + ); +}); diff --git a/testing/these.test.ts b/testing/these.test.ts index ee6963c..de7b4e8 100644 --- a/testing/these.test.ts +++ b/testing/these.test.ts @@ -84,16 +84,16 @@ Deno.test("These traverse", () => { assertEquals(t2(T.both(1, 1)), O.some(T.both(1, 1))); }); -Deno.test("These getShowable", () => { +Deno.test("These getShowableThese", () => { const f = { show: (n: number) => n.toString() }; - const { show } = T.getShowable(f, f); + const { show } = T.getShowableThese(f, f); assertEquals(show(T.left(1)), "Left(1)"); assertEquals(show(T.right(1)), "Right(1)"); assertEquals(show(T.both(1, 1)), "Both(1, 1)"); }); -Deno.test("These getCombinable", () => { - const Combinable = T.getCombinable( +Deno.test("These getCombinableThese", () => { + const Combinable = T.getCombinableThese( InitializableNumberSum, InitializableNumberSum, ); @@ -112,11 +112,33 @@ Deno.test("These getCombinable", () => { assertEquals(cb(T.both(1, 1)), T.both(2, 2)); }); -Deno.test("These getRightFlatmappable", () => { +Deno.test("These getInitializableThese", () => { + const Combinable = T.getInitializableThese( + InitializableNumberSum, + InitializableNumberSum, + ); + const combine = Combinable.combine; + const cl = combine(T.left(1)); + const cr = combine(T.right(1)); + const cb = combine(T.both(1, 1)); + + assertEquals(Combinable.init(), T.both(0, 0)); + assertEquals(cl(T.left(1)), T.left(2)); + assertEquals(cl(T.right(1)), T.both(1, 1)); + assertEquals(cl(T.both(1, 1)), T.both(2, 1)); + assertEquals(cr(T.left(1)), T.both(1, 1)); + assertEquals(cr(T.right(1)), T.right(2)); + assertEquals(cr(T.both(1, 1)), T.both(1, 2)); + assertEquals(cb(T.left(1)), T.both(2, 1)); + assertEquals(cb(T.right(1)), T.both(1, 2)); + assertEquals(cb(T.both(1, 1)), T.both(2, 2)); +}); + +Deno.test("These getFlatmappableRight", () => { const INC = (n: number) => n + 1; type INC = typeof INC; - const { apply, flatmap, map, wrap } = T.getRightFlatmappable( + const { apply, flatmap, map, wrap } = T.getFlatmappableRight( InitializableNumberSum, ); diff --git a/these.ts b/these.ts index f0e44eb..08ace09 100644 --- a/these.ts +++ b/these.ts @@ -1,8 +1,18 @@ +/** + * This file contains the These algebraic data type. These is a sort of + * combination of Either and Pair. It can represent a complete failure or a + * partial failure. + * + * @module These + * @since 2.0.0 + */ + import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Bimappable } from "./bimappable.ts"; import type { Combinable } from "./combinable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Initializable } from "./initializable.ts"; import type { Mappable } from "./mappable.ts"; import type { Foldable } from "./foldable.ts"; import type { Showable } from "./showable.ts"; @@ -11,34 +21,64 @@ import type { Traversable } from "./traversable.ts"; import * as E from "./either.ts"; import { pipe } from "./fn.ts"; +/** + * @since 2.0.0 + */ export type Left = E.Left; +/** + * @since 2.0.0 + */ export type Right = E.Right; +/** + * @since 2.0.0 + */ export type Both = { tag: "Both"; left: B; right: A }; +/** + * @since 2.0.0 + */ export type These = Left | Right | Both; +/** + * @since 2.0.0 + */ export interface KindThese extends Kind { readonly kind: These, Out>; } +/** + * @since 2.0.0 + */ export interface KindRightThese extends Kind { readonly kind: These>; } +/** + * @since 2.0.0 + */ export function left(left: B): These { return ({ tag: "Left", left }); } +/** + * @since 2.0.0 + */ export function right(right: A): These { return ({ tag: "Right", right }); } +/** + * @since 2.0.0 + */ export function both(left: B, right: A): These { return ({ tag: "Both", left, right }); } +/** + * @since 2.0.0 + */ export function match( onLeft: (b: B) => O, onRight: (a: A) => O, @@ -48,26 +88,44 @@ export function match( return (ta) => ta.tag === "Both" ? onBoth(ta.left, ta.right) : _fold(ta); } +/** + * @since 2.0.0 + */ export function isLeft(ta: These): ta is Left { return ta.tag === "Left"; } +/** + * @since 2.0.0 + */ export function isRight(ta: These): ta is Right { return ta.tag === "Right"; } +/** + * @since 2.0.0 + */ export function isBoth(ta: These): ta is Both { return ta.tag === "Both"; } +/** + * @since 2.0.0 + */ export function wrap(a: A): These { return right(a); } +/** + * @since 2.0.0 + */ export function fail(b: B): These { return left(b); } +/** + * @since 2.0.0 + */ export function map( fai: (a: A) => I, ): (ta: These) => These { @@ -78,6 +136,9 @@ export function map( ); } +/** + * @since 2.0.0 + */ export function mapSecond( fbj: (b: B) => J, ): (ta: These) => These { @@ -88,6 +149,9 @@ export function mapSecond( ); } +/** + * @since 2.0.0 + */ export function fold( foao: (o: O, a: A) => O, o: O, @@ -95,6 +159,9 @@ export function fold( return (ta) => isLeft(ta) ? o : foao(o, ta.right); } +/** + * @since 2.0.0 + */ export function traverse( A: Applicable, ): ( @@ -108,19 +175,10 @@ export function traverse( ); } -export const BimappableThese: Bimappable = { map, mapSecond }; - -export const MappableThese: Mappable = { map }; - -export const FoldableThese: Foldable = { fold }; - -export const TraversableThese: Traversable = { - map, - fold, - traverse, -}; - -export function getShowable( +/** + * @since 2.0.0 + */ +export function getShowableThese( SB: Showable, SA: Showable, ): Showable> { @@ -133,9 +191,12 @@ export function getShowable( }); } -export function getCombinable( - SB: Combinable, +/** + * @since 2.0.0 + */ +export function getCombinableThese( SA: Combinable, + SB: Combinable, ): Combinable> { return ({ combine: (x) => (y) => { @@ -167,7 +228,23 @@ export function getCombinable( }); } -export function getRightFlatmappable( +/** + * @since 2.0.0 + */ +export function getInitializableThese( + IA: Initializable, + IB: Initializable, +): Initializable> { + return { + init: () => both(IB.init(), IA.init()), + ...getCombinableThese(IA, IB), + }; +} + +/** + * @since 2.0.0 + */ +export function getFlatmappableRight( { combine }: Combinable, ): Flatmappable> { const Flatmappable: Flatmappable> = { @@ -217,3 +294,27 @@ export function getRightFlatmappable( }; return Flatmappable; } + +/** + * @since 2.0.0 + */ +export const BimappableThese: Bimappable = { map, mapSecond }; + +/** + * @since 2.0.0 + */ +export const MappableThese: Mappable = { map }; + +/** + * @since 2.0.0 + */ +export const FoldableThese: Foldable = { fold }; + +/** + * @since 2.0.0 + */ +export const TraversableThese: Traversable = { + map, + fold, + traverse, +}; diff --git a/tree.ts b/tree.ts index 6ea7099..80c71c2 100644 --- a/tree.ts +++ b/tree.ts @@ -1,6 +1,6 @@ /** - * This file contains a collection of utilities and - * algebraic structure implementations for Tree. + * This file contains a collection of utilities and algebraic structure + * implementations for Tree. * * @module Tree * @since 2.0.0 @@ -10,9 +10,11 @@ import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Comparable, Compare } from "./comparable.ts"; import type { Flatmappable } from "./flatmappable.ts"; +import type { Foldable } from "./foldable.ts"; import type { Mappable } from "./mappable.ts"; import type { Showable } from "./showable.ts"; import type { Traversable } from "./traversable.ts"; +import type { Wrappable } from "./wrappable.ts"; import { TraversableArray } from "./array.ts"; import { fromCompare } from "./comparable.ts"; @@ -265,6 +267,14 @@ export const FlatmappableTree: Flatmappable = { wrap, }; +/** + * @since 2.0.0 + */ +export const FoldableTree: Foldable = { fold }; + +/** + * @since 2.0.0 + */ export const MappableTree: Mappable = { map }; /** @@ -272,6 +282,11 @@ export const MappableTree: Mappable = { map }; */ export const TraversableTree: Traversable = { map, fold, traverse }; +/** + * @since 2.0.0 + */ +export const WrappableTree: Wrappable = { wrap }; + /** * @since 2.0.0 */