diff --git a/async.ts b/async.ts index b9528a5..bfa0aef 100644 --- a/async.ts +++ b/async.ts @@ -4,6 +4,8 @@ import type { Sync } from "./sync.ts"; import { resolve, wait } from "./promise.ts"; import { handleThrow } from "./fn.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; export type Async = Sync>; @@ -78,3 +80,9 @@ export const MonadAsyncSequential: Monad = { join, chain, }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadAsyncSequential); + +export const bindTo = bindTo_(MonadAsyncSequential); diff --git a/async_either.ts b/async_either.ts index dfdd559..6200287 100644 --- a/async_either.ts +++ b/async_either.ts @@ -20,7 +20,8 @@ import * as A from "./async.ts"; import * as P from "./promise.ts"; import { handleThrow, identity, pipe } from "./fn.ts"; import { resolve } from "./promise.ts"; - +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; /** * The AsyncEither type can best be thought of as an asynchronous function that * returns an `Either`. ie. `async () => Promise>`. This @@ -195,9 +196,11 @@ export function throwError(b: B): AsyncEither { * @example * ```ts * import * as AE from "./async_either.ts"; - * import { } from "./fn.ts"; + * import { flow } from "./fn.ts"; * - * const work = f + * const work = flow(AE.bimap((x: string) => x.length, (x: number) => x * 2)); + * const left = await work(AE.left("Error"))(); // Left(5) + * const result = await work(AE.right(1))(); // Right(2) * ``` * * @since 2.0.0 @@ -396,17 +399,17 @@ export function alt( * * ```ts * import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; - * import * as TE from "./async_either.ts"; - * import * as T from "./async.ts"; + * import * as AE from "./async_either.ts"; + * import * as A from "./async.ts"; * import { flow, identity } from "./fn.ts"; * * const hello = flow( - * TE.match(() => "World", identity), + * AE.match(() => "World", identity), * A.map((name) => `Hello ${name}!`), * ); * - * assertEquals(await hello(TE.right("Functional!"))(), "Hello Functional!!"); - * assertEquals(await hello(TE.left(Error))(), "Hello World!"); + * assertEquals(await hello(AE.right("Functional!"))(), "Hello Functional!!"); + * assertEquals(await hello(AE.left(Error))(), "Hello World!"); * ``` * * @since 2.0.0 @@ -445,3 +448,9 @@ export const MonadAsyncEitherSequential: Monad = { join, chain, }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadAsyncEitherSequential); + +export const bindTo = bindTo_(MonadAsyncEitherSequential); diff --git a/chain.ts b/chain.ts index e2c040e..de4b38c 100644 --- a/chain.ts +++ b/chain.ts @@ -12,3 +12,55 @@ export interface Chain extends TypeClass, Apply { ta: $, ) => $; } + +/** + * ChainRec + * https://github.com/fantasyland/static-land/blob/master/docs/spec.md#chain + */ +export interface ChainRec extends TypeClass, Chain { + readonly chainRec: ( + fati: (a: A) => $, + a: A, + ) => $; +} + +// chainFirst +export function chainFirst( + M: Chain, +): ( + f: (a: A) => $, +) => (ma: $) => $ { + // @ts-expect-error TODO: @baetheus + return (f) => (ma) => M.chain((a) => M.map(() => a)(f(a)))(ma); +} + +// bind +export function bind( + M: Chain, +): < + N extends string, + A, + B, + C, + I, + J = never, + K = never, + D = unknown, + E = unknown, +>( + name: Exclude, + f: (a: A) => $, +) => ( + ma: $, +) => $< + U, + [{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }], + [D], + [E] +> { + return (name, f) => + // @ts-expect-error TODO: @baetheus + M.chain((a) => + M.map((b) => Object.assign({}, a, { [name]: b }) as any)(f(a)) + ); +} diff --git a/datum.ts b/datum.ts index 2e67a92..9ed56c0 100644 --- a/datum.ts +++ b/datum.ts @@ -12,6 +12,8 @@ import type { Traversable } from "./traversable.ts"; import { fromCompare } from "./ord.ts"; import { isNotNil } from "./nilable.ts"; import { flow, identity, pipe } from "./fn.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; /** * TODO: Lets get a monoid in here for tracking progress. @@ -52,11 +54,11 @@ export const initial: Initial = { tag: "Initial" }; export const pending: Pending = { tag: "Pending" }; export function refresh(value: D): Datum { - return ({ tag: "Refresh", value }); + return { tag: "Refresh", value }; } export function replete(value: D): Datum { - return ({ tag: "Replete", value }); + return { tag: "Replete", value }; } export function constInitial(): Datum { @@ -80,15 +82,7 @@ export function tryCatch(fa: () => A): Datum { } export function toLoading(ta: Datum): Datum { - return pipe( - ta, - match( - constPending, - constPending, - refresh, - refresh, - ), - ); + return pipe(ta, match(constPending, constPending, refresh, refresh)); } export function isInitial(ta: Datum): ta is Initial { @@ -160,12 +154,10 @@ export function map(fai: (a: A) => I): (ta: Datum) => Datum { ); } -export function ap( - ua: Datum, -): (ufai: Datum<(a: A) => I>) => Datum { +export function ap(ua: Datum): (ufai: Datum<(a: A) => I>) => Datum { switch (ua.tag) { case "Initial": - return (ufai) => isLoading(ufai) ? pending : initial; + return (ufai) => (isLoading(ufai) ? pending : initial); case "Pending": return constPending; case "Replete": @@ -178,19 +170,14 @@ export function ap( ? pending : initial; case "Refresh": - return (ufai) => isSome(ufai) ? refresh(ufai.value(ua.value)) : pending; + return (ufai) => (isSome(ufai) ? refresh(ufai.value(ua.value)) : pending); } } export function chain( fati: (a: A) => Datum, ): (ta: Datum) => Datum { - return match( - constInitial, - constPending, - fati, - flow(fati, toLoading), - ); + return match(constInitial, constPending, fati, flow(fati, toLoading)); } export function join(taa: Datum>): Datum { @@ -198,14 +185,14 @@ export function join(taa: Datum>): Datum { } export function alt(tb: Datum): (ta: Datum) => Datum { - return (ta) => isSome(ta) ? ta : tb; + return (ta) => (isSome(ta) ? ta : tb); } export function reduce( foao: (o: O, a: A) => O, o: O, ): (ta: Datum) => O { - return (ta) => isSome(ta) ? foao(o, ta.value) : o; + return (ta) => (isSome(ta) ? foao(o, ta.value) : o); } export function traverse( @@ -217,58 +204,66 @@ export function traverse( match( () => A.of(constInitial()), () => A.of(constPending()), - (a) => pipe(favi(a), A.map((i) => replete(i))), - (a) => pipe(favi(a), A.map((i) => refresh(i))), + (a) => + pipe( + favi(a), + A.map((i) => replete(i)), + ), + (a) => + pipe( + favi(a), + A.map((i) => refresh(i)), + ), ); } export function getShow({ show }: Show): Show> { - return ({ + return { show: match( () => `Initial`, () => `Pending`, (a) => `Replete(${show(a)})`, (a) => `Refresh(${show(a)})`, ), - }); + }; } -export function getSemigroup( - S: Semigroup, -): Semigroup> { - return ({ +export function getSemigroup(S: Semigroup): Semigroup> { + return { concat: (mx) => match( () => mx, () => toLoading(mx), (v) => isSome(mx) - ? (isRefresh(mx) + ? isRefresh(mx) ? refresh(S.concat(mx.value)(v)) - : replete(S.concat(mx.value)(v))) - : (isPending(mx) ? refresh(v) : replete(v)), - (v) => isSome(mx) ? refresh(S.concat(mx.value)(v)) : refresh(v), + : replete(S.concat(mx.value)(v)) + : isPending(mx) + ? refresh(v) + : replete(v), + (v) => (isSome(mx) ? refresh(S.concat(mx.value)(v)) : refresh(v)), ), - }); + }; } export function getMonoid(S: Semigroup): Monoid> { - return ({ + return { ...getSemigroup(S), empty: constInitial, - }); + }; } export function getEq(S: Eq): Eq> { - return ({ + return { equals: (b) => match( () => isInitial(b), () => isPending(b), - (v) => isReplete(b) ? S.equals(b.value)(v) : false, - (v) => isRefresh(b) ? S.equals(b.value)(v) : false, + (v) => (isReplete(b) ? S.equals(b.value)(v) : false), + (v) => (isRefresh(b) ? S.equals(b.value)(v) : false), ), - }); + }; } export function getOrd(O: Ord): Ord> { @@ -276,11 +271,11 @@ export function getOrd(O: Ord): Ord> { pipe( fst, match( - () => isInitial(snd) ? 0 : -1, - () => isInitial(snd) ? 1 : isPending(snd) ? 0 : -1, + () => (isInitial(snd) ? 0 : -1), + () => (isInitial(snd) ? 1 : isPending(snd) ? 0 : -1), (value) => isNone(snd) ? 1 : isReplete(snd) ? O.compare(value, snd.value) : -1, - (value) => isRefresh(snd) ? O.compare(value, snd.value) : 1, + (value) => (isRefresh(snd) ? O.compare(value, snd.value) : 1), ), ) ); @@ -295,3 +290,9 @@ export const TraversableDatum: Traversable = { reduce, traverse, }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadDatum); + +export const bindTo = bindTo_(MonadDatum); diff --git a/either.ts b/either.ts index e360dbb..bb154f6 100644 --- a/either.ts +++ b/either.ts @@ -17,6 +17,8 @@ import * as O from "./option.ts"; import { isNotNil } from "./nilable.ts"; import { fromCompare } from "./ord.ts"; import { flow, pipe } from "./fn.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; export type Left = { tag: "Left"; left: L }; @@ -33,11 +35,11 @@ export interface KindRightEither extends Kind { } export function left(left: E): Either { - return ({ tag: "Left", left }); + return { tag: "Left", left }; } export function right(right: A): Either { - return ({ tag: "Right", right }); + return { tag: "Right", right }; } export function of(a: A): Either { @@ -49,9 +51,8 @@ export function throwError(b: B): Either { } export function fromNullable(fe: () => E) { - return ( - a: A, - ): Either> => (isNotNil(a) ? right(a) : left(fe())); + return (a: A): Either> => + isNotNil(a) ? right(a) : left(fe()); } export function tryCatch( @@ -86,18 +87,18 @@ export function fromPredicate( predicate: Predicate, onFalse: (a: A) => E, ): (a: A) => Either { - return (a: A) => predicate(a) ? right(a) : left(onFalse(a)); + return (a: A) => (predicate(a) ? right(a) : left(onFalse(a))); } export function match( onLeft: (left: L) => B, onRight: (right: R) => B, ): (ta: Either) => B { - return (ta) => isLeft(ta) ? onLeft(ta.left) : onRight(ta.right); + return (ta) => (isLeft(ta) ? onLeft(ta.left) : onRight(ta.right)); } export function getOrElse(onLeft: (e: E) => A) { - return (ma: Either): A => isLeft(ma) ? onLeft(ma.left) : ma.right; + return (ma: Either): A => (isLeft(ma) ? onLeft(ma.left) : ma.right); } export function getRight(ma: Either): O.Option { @@ -116,23 +117,17 @@ export function isRight(m: Either): m is Right { return m.tag === "Right"; } -export function getShow( - SB: Show, - SA: Show, -): Show> { - return ({ +export function getShow(SB: Show, SA: Show): Show> { + return { show: match( (left) => `Left(${SB.show(left)})`, (right) => `Right(${SA.show(right)})`, ), - }); + }; } -export function getEq( - SB: Eq, - SA: Eq, -): Eq> { - return ({ +export function getEq(SB: Eq, SA: Eq): Eq> { + return { equals: (b) => (a) => { if (isLeft(a)) { if (isLeft(b)) { @@ -146,13 +141,10 @@ export function getEq( } return SA.equals(b.right)(a.right); }, - }); + }; } -export function getOrd( - OB: Ord, - OA: Ord, -): Ord> { +export function getOrd(OB: Ord, OA: Ord): Ord> { return fromCompare((fst, snd) => isLeft(fst) ? isLeft(snd) ? OB.compare(fst.left, snd.left) : -1 @@ -165,50 +157,52 @@ export function getOrd( export function getLeftSemigroup( SE: Semigroup, ): Semigroup> { - return ({ + return { concat: (x) => (y) => isRight(x) ? x : isRight(y) ? y : left(SE.concat(x.left)(y.left)), - }); + }; } export function getRightSemigroup( SA: Semigroup, ): Semigroup> { - return ({ + return { concat: (x) => (y) => isLeft(x) ? x : isLeft(y) ? y : right(SA.concat(x.right)(y.right)), - }); + }; } export function getRightMonoid( MA: Monoid, ): Monoid> { - return ({ + return { ...getRightSemigroup(MA), empty: () => right(MA.empty()), - }); + }; } -export function getRightMonad( - { concat }: Semigroup, -): Monad> { - return ({ +export function getRightMonad({ + concat, +}: Semigroup): Monad> { + return { of, ap: (ua) => (ufai) => isLeft(ufai) - ? (isLeft(ua) ? left(concat(ua.left)(ufai.left)) : ufai) - : (isLeft(ua) ? ua : right(ufai.right(ua.right))), + ? isLeft(ua) ? left(concat(ua.left)(ufai.left)) : ufai + : isLeft(ua) + ? ua + : right(ufai.right(ua.right)), map, join, chain, - }); + }; } export function bimap( fbj: (b: B) => J, fai: (a: A) => I, ): (ta: Either) => Either { - return (ta) => isLeft(ta) ? left(fbj(ta.left)) : right(fai(ta.right)); + return (ta) => (isLeft(ta) ? left(fbj(ta.left)) : right(fai(ta.right))); } export function swap(ma: Either): Either { @@ -225,7 +219,7 @@ export function stringifyJSON( export function map( fai: (a: A) => I, ): (ta: Either) => Either { - return (ta) => isLeft(ta) ? ta : right(fai(ta.right)); + return (ta) => (isLeft(ta) ? ta : right(fai(ta.right))); } export function chainLeft( @@ -244,7 +238,7 @@ export function ap( export function chain( fati: (a: A) => Either, ): (ta: Either) => Either { - return (ta) => isLeft(ta) ? ta : fati(ta.right); + return (ta) => (isLeft(ta) ? ta : fati(ta.right)); } export function join( @@ -256,13 +250,13 @@ export function join( export function mapLeft( fbj: (b: B) => J, ): (ta: Either) => Either { - return (ta) => isLeft(ta) ? left(fbj(ta.left)) : ta; + return (ta) => (isLeft(ta) ? left(fbj(ta.left)) : ta); } export function alt( tb: Either, ): (ta: Either) => Either { - return (ta) => isLeft(ta) ? tb : ta; + return (ta) => (isLeft(ta) ? tb : ta); } export function extend( @@ -275,7 +269,7 @@ export function reduce( foao: (o: O, a: A) => O, o: O, ): (ta: Either) => O { - return (ta) => isLeft(ta) ? o : foao(o, ta.right); + return (ta) => (isLeft(ta) ? o : foao(o, ta.right)); } export function traverse( @@ -298,6 +292,12 @@ export const MonadEither: Monad = { chain, }; +export const Do = () => of( {}); + +export const bind = bind_(MonadEither); + +export const bindTo = bindTo_(MonadEither); + export const BifunctorEither: Bifunctor = { bimap, mapLeft }; export const AltEither: Alt = { alt, map }; diff --git a/examples/category.ts b/examples/category.ts index e22c9f4..018a11a 100644 --- a/examples/category.ts +++ b/examples/category.ts @@ -20,5 +20,3 @@ export const getCategoryMonoid = (M: Monoid): Category> => ({ id: M.empty, compose: M.concat, }); - - diff --git a/fn_either.ts b/fn_either.ts index eaae2b2..d288a16 100644 --- a/fn_either.ts +++ b/fn_either.ts @@ -19,6 +19,8 @@ import type { Predicate } from "./predicate.ts"; import type { Refinement } from "./refinement.ts"; import type { Fn } from "./fn.ts"; import type { Either } from "./either.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; import * as E from "./either.ts"; import * as F from "./fn.ts"; @@ -698,3 +700,9 @@ export function getRightMonad( chain, }); } + +export const Do = () => of( {}); + +export const bind = bind_(MonadFnEither); + +export const bindTo = bindTo_(MonadFnEither); diff --git a/functor.ts b/functor.ts index 472ab23..2514ebf 100644 --- a/functor.ts +++ b/functor.ts @@ -11,3 +11,13 @@ export interface Functor extends TypeClass { ta: $, ) => $; } + +export function bindTo( + F: Functor, +): ( + name: N, +) => ( + fa: $, +) => $ { + return (name) => F.map((a) => ({ [name]: a } as any)); +} diff --git a/identity.ts b/identity.ts index 63770c1..2306468 100644 --- a/identity.ts +++ b/identity.ts @@ -1,6 +1,9 @@ import type { Kind, Out } from "./kind.ts"; import type { Monad } from "./monad.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; + export type Identity = A; export interface KindIdentity extends Kind { @@ -11,9 +14,7 @@ export function of(a: A): Identity { return a; } -export function map( - fai: (a: A) => I, -): (ta: Identity) => Identity { +export function map(fai: (a: A) => I): (ta: Identity) => Identity { return fai; } @@ -34,3 +35,9 @@ export function chain( } export const MonadIdentity: Monad = { of, ap, map, join, chain }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadIdentity); + +export const bindTo = bindTo_(MonadIdentity); diff --git a/nilable.ts b/nilable.ts index 4786d01..60a6345 100644 --- a/nilable.ts +++ b/nilable.ts @@ -11,6 +11,8 @@ import type { Kind, Out } from "./kind.ts"; import type { Monad } from "./monad.ts"; import type { Predicate } from "./predicate.ts"; import type { Show } from "./show.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; import { identity, pipe } from "./fn.ts"; @@ -107,6 +109,12 @@ export function alt(tb: Nilable): (ta: Nilable) => Nilable { export const MonadNilable: Monad = { of, ap, map, join, chain }; +export const Do = () => of( {}); + +export const bind = bind_(MonadNilable); + +export const bindTo = bindTo_(MonadNilable); + export const AltNilable: Alt = { alt, map }; export function getShow({ show }: Show): Show> { diff --git a/option.ts b/option.ts index 6b5a3a5..d02b1cd 100644 --- a/option.ts +++ b/option.ts @@ -30,6 +30,8 @@ import type { Traversable } from "./traversable.ts"; import { isNotNil } from "./nilable.ts"; import { fromCompare } from "./ord.ts"; import { flow, handleThrow, identity, pipe } from "./fn.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; /** * The None type represents the non-existence of a value. @@ -103,7 +105,7 @@ export const none: Option = { tag: "None" }; * @since 2.0.0 */ export function some(value: A): Option { - return ({ tag: "Some", value }); + return { tag: "Some", value }; } /** @@ -157,9 +159,7 @@ export function fromNullable(a: A): Option> { export function fromPredicate( refinement: Refinement, ): (a: A) => Option; -export function fromPredicate( - refinement: Predicate, -): (a: A) => Option; +export function fromPredicate(refinement: Predicate): (a: A) => Option; export function fromPredicate(predicate: Predicate) { return (a: A): Option => (predicate(a) ? some(a) : none); } @@ -234,7 +234,7 @@ export function match(onNone: () => B, onSome: (a: A) => B) { * @since 2.0.0 */ export function getOrElse(onNone: () => B) { - return (ta: Option): B => isNone(ta) ? onNone() : ta.value; + return (ta: Option): B => (isNone(ta) ? onNone() : ta.value); } /** @@ -358,7 +358,7 @@ export function of(a: A): Option { * @since 2.0.0 */ export function map(fai: (a: A) => I): (ua: Option) => Option { - return (ua) => isNone(ua) ? none : some(fai(ua.value)); + return (ua) => (isNone(ua) ? none : some(fai(ua.value))); } /** @@ -443,7 +443,7 @@ export function ap( export function chain( fati: (a: A) => Option, ): (ta: Option) => Option { - return (ua) => isNone(ua) ? ua : fati(ua.value); + return (ua) => (isNone(ua) ? ua : fati(ua.value)); } /** @@ -482,7 +482,7 @@ export function join(taa: Option>): Option { * @since 2.0.0 */ export function alt(second: Option): (first: Option) => Option { - return (first) => isNone(first) ? second : first; + return (first) => (isNone(first) ? second : first); } /** @@ -559,7 +559,7 @@ export function filter( predicate: Predicate, ): (ta: Option) => Option { const _exists = exists(predicate); - return (ta) => _exists(ta) ? ta : none; + return (ta) => (_exists(ta) ? ta : none); } /** @@ -677,7 +677,7 @@ export function reduce( reducer: (accumulator: O, current: A) => O, initial: O, ): (ua: Option) => O { - return (ua) => isSome(ua) ? reducer(initial, ua.value) : initial; + return (ua) => (isSome(ua) ? reducer(initial, ua.value) : initial); } /** @@ -818,9 +818,9 @@ export const TraversableOption: Traversable = { * @since 2.0.0 */ export function getShow({ show }: Show): Show> { - return ({ + return { show: (ma) => (isNone(ma) ? "None" : `${"Some"}(${show(ma.value)})`), - }); + }; } /** @@ -842,13 +842,13 @@ export function getShow({ show }: Show): Show> { * @since 2.0.0 */ export function getEq(S: Eq): Eq> { - return ({ + return { equals: (a) => (b) => a === b || - ((isSome(a) && isSome(b)) + (isSome(a) && isSome(b) ? S.equals(a.value)(b.value) - : (isNone(a) && isNone(b))), - }); + : isNone(a) && isNone(b)), + }; } /** @@ -897,13 +897,11 @@ export function getOrd(O: Ord): Ord> { * * @since 2.0.0 */ -export function getSemigroup( - S: Semigroup, -): Semigroup> { - return ({ +export function getSemigroup(S: Semigroup): Semigroup> { + return { concat: (x) => (y) => isNone(x) ? y : isNone(y) ? x : of(S.concat(x.value)(y.value)), - }); + }; } /** @@ -927,8 +925,14 @@ export function getSemigroup( * @since 2.0.0 */ export function getMonoid(M: Monoid): Monoid> { - return ({ + return { ...getSemigroup(M), empty: constNone, - }); + }; } + +export const Do = () => of( {}); + +export const bind = bind_(MonadOption); + +export const bindTo = bindTo_(MonadOption); diff --git a/record.ts b/record.ts index 584f7d6..eade3cb 100644 --- a/record.ts +++ b/record.ts @@ -217,10 +217,7 @@ export function map( * * @since 2.0.0 */ -export function reduce( - foao: (o: O, a: A, i: string) => O, - o: O, -) { +export function reduce(foao: (o: O, a: A, i: string) => O, o: O) { return (rec: ReadonlyRecord): O => { let result = o; for (const key in rec) { @@ -281,13 +278,8 @@ export function collect( * * @since 2.0.0 */ -export function collapse( - M: Monoid, -): (ua: ReadonlyRecord) => A { - return reduce( - (first: A, second: A) => M.concat(second)(first), - M.empty(), - ); +export function collapse(M: Monoid): (ua: ReadonlyRecord) => A { + return reduce((first: A, second: A) => M.concat(second)(first), M.empty()); } /** @@ -340,11 +332,7 @@ export function traverse( a: A, key: string, ): $, J, K], [L], [M]> => - pipe( - vis, - A.map(pusher(key)), - A.ap(favi(a, key)), - ); + pipe(vis, A.map(pusher(key)), A.ap(favi(a, key))); return (ua) => pipe(ua, reduce(reducer, A.of({}))); }; @@ -366,14 +354,53 @@ export function traverse( * Either */ // deno-fmt-ignore -type Sequence>> = $ ? A : never; }, - { [K in keyof R]: R[K] extends $ ? B : never; }[keyof R], - { [K in keyof R]: R[K] extends $ ? C : never; }[keyof R], - ], [ - Intersect< { [K in keyof R]: R[K] extends $ ? D : never; }[keyof R] >, - ], [ - Intersect< { [K in keyof R]: R[K] extends $ ? E : never; }[keyof R] >, +type Sequence>> = $< + U, + [ + { + [K in keyof R]: R[K] extends $< + U, + [infer A, infer _, infer _], + any[], + any[] + > + ? A + : never; + }, + { + [K in keyof R]: R[K] extends $< + U, + [infer _, infer B, infer _], + any[], + any[] + > + ? B + : never; + }[keyof R], + { + [K in keyof R]: R[K] extends $< + U, + [infer _, infer _, infer C], + any[], + any[] + > + ? C + : never; + }[keyof R] + ], + [ + Intersect< + { + [K in keyof R]: R[K] extends $ ? D : never; + }[keyof R] + > + ], + [ + Intersect< + { + [K in keyof R]: R[K] extends $ ? E : never; + }[keyof R] + > ] >; @@ -675,9 +702,7 @@ export function lookupWithKey(key: string) { * @since 2.0.0 */ export function deleteAt(key: string) { - return ( - rec: ReadonlyRecord, - ): ReadonlyRecord => { + return (rec: ReadonlyRecord): ReadonlyRecord => { if (Object.hasOwn(rec, key)) { const out = { ...rec }; delete out[key]; @@ -711,9 +736,7 @@ export function deleteAt(key: string) { * @since 2.0.0 */ export function deleteAtWithValue(key: string) { - return ( - rec: ReadonlyRecord, - ): Pair, Option> => { + return (rec: ReadonlyRecord): Pair, Option> => { if (Object.hasOwn(rec, key)) { const out = { ...rec }; const value = rec[key]; @@ -986,11 +1009,12 @@ export const TraversableRecord: Traversable = { * @since 2.0.0 */ export function getShow(SA: Show): Show> { - return ({ + return { show: (ua) => `{${ - Object.entries(ua).map(([key, value]) => `${key}: ${SA.show(value)}`) + Object.entries(ua) + .map(([key, value]) => `${key}: ${SA.show(value)}`) .join(", ") }}`, - }); + }; } diff --git a/state.ts b/state.ts index 028417a..06ef434 100644 --- a/state.ts +++ b/state.ts @@ -12,6 +12,8 @@ import type { InOut, Kind, Out } from "./kind.ts"; import type { Monad } from "./monad.ts"; import { flow, identity, pipe } from "./fn.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; /** * The State type represents the core State structure. The input/output @@ -315,3 +317,9 @@ export function execute(s: S): (ta: State) => S { * @since 2.0.0 */ export const MonadState: Monad = { of, ap, map, join, chain }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadState); + +export const bindTo = bindTo_(MonadState); diff --git a/sync.ts b/sync.ts index 7f17aa0..afb0dfa 100644 --- a/sync.ts +++ b/sync.ts @@ -5,6 +5,8 @@ import type { Monad } from "./monad.ts"; import type { Traversable } from "./traversable.ts"; import { constant, flow, pipe } from "./fn.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; export type Sync = () => A; @@ -58,3 +60,9 @@ export const MonadSync: Monad = { of, ap, map, join, chain }; export const ExtendsSync: Extend = { map, extend }; export const TraversableSync: Traversable = { map, reduce, traverse }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadSync); + +export const bindTo = bindTo_(MonadSync); diff --git a/sync_either.ts b/sync_either.ts index d82f868..746012c 100644 --- a/sync_either.ts +++ b/sync_either.ts @@ -9,6 +9,9 @@ import type { Either } from "./either.ts"; import * as E from "./either.ts"; import * as I from "./sync.ts"; +import { bind as bind_ } from "./chain.ts"; +import { bindTo as bindTo_ } from "./functor.ts"; + import { constant, flow, identity, pipe } from "./fn.ts"; export type SyncEither = Sync>; @@ -119,7 +122,14 @@ export function reduce( foao: (o: O, a: A) => O, o: O, ): (ta: SyncEither) => O { - return (ta) => pipe(ta(), E.match(() => o, (a) => foao(o, a))); + return (ta) => + pipe( + ta(), + E.match( + () => o, + (a) => foao(o, a), + ), + ); } export const BifunctorSyncEither: Bifunctor = { @@ -140,3 +150,9 @@ export const AltSyncEither: Alt = { alt, map }; export const ExtendsSyncEither: Extend = { map, extend }; export const FoldableSyncEither: Foldable = { reduce }; + +export const Do = () => of( {}); + +export const bind = bind_(MonadSyncEither); + +export const bindTo = bindTo_(MonadSyncEither); diff --git a/testing/async.test.ts b/testing/async.test.ts index fd26974..b800ba7 100644 --- a/testing/async.test.ts +++ b/testing/async.test.ts @@ -19,6 +19,9 @@ function throwAsync(n: number): Promise { return Promise.resolve(n); } +const assertEqualsT = async (actual: A.Async, expected: A.Async) => + assertEquals(await actual(), await expected()); + Deno.test("Async of", async () => { assertEquals(await A.of(0)(), 0); }); @@ -44,7 +47,10 @@ Deno.test("Async of", async () => { Deno.test("Async apParallel", async () => { assertEquals( - await pipe(A.of((n: number) => n + 1), A.apParallel(A.of(1)))(), + await pipe( + A.of((n: number) => n + 1), + A.apParallel(A.of(1)), + )(), 2, ); }); @@ -58,31 +64,34 @@ Deno.test("Async join", async () => { }); Deno.test("Async chain", async () => { - assertEquals(await pipe(A.of(1), A.chain((n) => A.of(n + 1)))(), 2); + assertEquals( + await pipe( + A.of(1), + A.chain((n) => A.of(n + 1)), + )(), + 2, + ); }); Deno.test("Async apSeq", async () => { assertEquals( - await pipe(A.of((n: number) => n + 1), A.apSequential(A.of(1)))(), + await pipe( + A.of((n: number) => n + 1), + A.apSequential(A.of(1)), + )(), 2, ); }); -// Deno.test("Async Do, bind, bindTo", () => { -// assertEquals( -// pipe( -// A.Do(), -// A.bind("one", () => A.of(1)), -// A.bind("two", ({ one }) => A.of(one + one)), -// A.map(({ one, two }) => one + two), -// ), -// A.of(3), -// ); -// assertEquals( -// pipe( -// A.of(1), -// A.bindTo("one"), -// ), -// A.of({ one: 1 }), -// ); -// }); +Deno.test("Async Do, bind, bindTo", () => { + assertEqualsT( + pipe( + A.Do(), + A.bind("one", () => A.of(1)), + A.bind("two", ({ one }) => A.of(one + one)), + A.map(({ one, two }) => one + two), + ), + A.of(3), + ); + assertEqualsT(pipe(A.of(1), A.bindTo("one")), A.of({ one: 1 })); +}); diff --git a/testing/async_either.test.ts b/testing/async_either.test.ts index 93ee3ab..69c24a6 100644 --- a/testing/async_either.test.ts +++ b/testing/async_either.test.ts @@ -155,21 +155,21 @@ 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 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 }), + ); +}); diff --git a/testing/datum.test.ts b/testing/datum.test.ts index 40dca62..6a174c8 100644 --- a/testing/datum.test.ts +++ b/testing/datum.test.ts @@ -308,21 +308,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.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 }), + ); +}); diff --git a/testing/either.test.ts b/testing/either.test.ts index 3fa2c1e..fe6da98 100644 --- a/testing/either.test.ts +++ b/testing/either.test.ts @@ -210,21 +210,21 @@ Deno.test("Either chainLeft", () => { assertEquals(chainLeft(E.left(1)), E.right(1)); }); -// 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.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 }), + ); +}); diff --git a/testing/nilable.test.ts b/testing/nilable.test.ts index 0e018de..3ed825b 100644 --- a/testing/nilable.test.ts +++ b/testing/nilable.test.ts @@ -30,11 +30,17 @@ Deno.test("Nilable fromPredicate", () => { Deno.test("Nilable tryCatch", () => { assertEquals(N.tryCatch(todo), N.nil); - assertEquals(N.tryCatch(() => 1), 1); + assertEquals( + N.tryCatch(() => 1), + 1, + ); }); Deno.test("Nilable match", () => { - const match = N.match(() => 0, (n: number) => n); + const match = N.match( + () => 0, + (n: number) => n, + ); assertEquals(match(null), 0); assertEquals(match(undefined), 0); assertEquals(match(1), 1); @@ -118,27 +124,21 @@ Deno.test("Nilable alt", () => { }); Deno.test("Nilable chain", () => { - const chain = N.chain((n: number) => n === 0 ? N.nil : n); + const chain = N.chain((n: number) => (n === 0 ? N.nil : n)); assertEquals(chain(undefined), N.nil); assertEquals(chain(null), N.nil); assertEquals(chain(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 }), -// ); -// }); +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/option.test.ts b/testing/option.test.ts index 62cb65e..126d9ad 100644 --- a/testing/option.test.ts +++ b/testing/option.test.ts @@ -39,7 +39,10 @@ Deno.test("Option tryCatch", () => { }); Deno.test("Option match", () => { - const match = O.match(() => 0, (n: number) => n); + const match = O.match( + () => 0, + (n: number) => n, + ); assertEquals(match(O.none), 0); assertEquals(match(O.some(1)), 1); }); @@ -104,7 +107,7 @@ Deno.test("Option join", () => { }); Deno.test("Option chain", () => { - const fati = (n: number) => n === 0 ? O.none : O.some(n); + const fati = (n: number) => (n === 0 ? O.none : O.some(n)); assertEquals(pipe(O.some(0), O.chain(fati)), O.none); assertEquals(pipe(O.some(1), O.chain(fati)), O.some(1)); assertEquals(pipe(O.none, O.chain(fati)), O.none); @@ -118,7 +121,7 @@ Deno.test("Option reduce", () => { Deno.test("Option traverse", () => { const t1 = O.traverse(O.MonadOption); - const t2 = t1((n: number) => n === 0 ? O.none : O.some(1)); + const t2 = t1((n: number) => (n === 0 ? O.none : O.some(1))); assertEquals(t2(O.none), O.some(O.none)); assertEquals(t2(O.some(0)), O.none); assertEquals(t2(O.some(1)), O.some(O.some(1))); @@ -168,7 +171,12 @@ Deno.test("Option partitionMap", () => { }); Deno.test("Option extend", () => { - const extend = O.extend(O.match(() => -1, (n: number) => n + 1)); + const extend = O.extend( + O.match( + () => -1, + (n: number) => n + 1, + ), + ); assertEquals(extend(O.some(0)), O.some(1)); assertEquals(extend(O.none), O.some(-1)); }); @@ -223,21 +231,15 @@ Deno.test("Option getMonoid", () => { assertEquals(empty(), 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 }), -// ); -// }); +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 })); +}); diff --git a/testing/state.test.ts b/testing/state.test.ts index 6951d3c..7437751 100644 --- a/testing/state.test.ts +++ b/testing/state.test.ts @@ -19,7 +19,10 @@ Deno.test("State put", () => { }); Deno.test("State modify", () => { - assertEqualsS(S.modify((n: number) => n + 1), S.put(2)); + assertEqualsS( + S.modify((n: number) => n + 1), + S.put(2), + ); }); Deno.test("State gets", () => { @@ -34,8 +37,7 @@ Deno.test("State of", () => { assertEqualsS(S.of(1), S.id()); }); -Deno.test("State ap", () => { -}); +Deno.test("State ap", () => {}); Deno.test("State map", () => { assertEqualsS(pipe(S.id(), S.map(add)), S.state(2, 1)); @@ -50,7 +52,10 @@ Deno.test("State join", () => { Deno.test("State chain", () => { assertEqualsS( - pipe(S.id(), S.chain((n) => S.gets((m) => n + m))), + pipe( + S.id(), + S.chain((n) => S.gets((m) => n + m)), + ), S.state(2, 1), ); }); @@ -63,21 +68,15 @@ 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 Do, bind, bindTo", () => { + assertEqualsS( + pipe( + S.Do(), + S.bind("one", () => S.state(1, 1)), + S.bind("two", ({ one }) => S.state(one + one, 1)), + S.map(({ one, two }) => one + two), + ), + S.state(3, 1), + ); + assertEqualsS(pipe(S.state(1, 1), S.bindTo("one")), S.state({ one: 1 }, 1)); +}); diff --git a/testing/sync_either.test.ts b/testing/sync_either.test.ts index d49f907..54a9322 100644 --- a/testing/sync_either.test.ts +++ b/testing/sync_either.test.ts @@ -125,21 +125,21 @@ Deno.test("SyncEither chainLeft", () => { assertEqualsIO(chainLeft(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("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 }), + ); +});