Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<A> = Sync<Promise<A>>;

Expand Down Expand Up @@ -78,3 +80,9 @@ export const MonadAsyncSequential: Monad<KindAsync> = {
join,
chain,
};

export const Do = <A>() => of<A>(<A> {});

export const bind = bind_(MonadAsyncSequential);

export const bindTo = bindTo_(MonadAsyncSequential);
25 changes: 17 additions & 8 deletions async_either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Either<B, A>>`. This
Expand Down Expand Up @@ -195,9 +196,11 @@ export function throwError<A = never, B = never>(b: B): AsyncEither<B, A> {
* @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
Expand Down Expand Up @@ -396,17 +399,17 @@ export function alt<I, J>(
*
* ```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
Expand Down Expand Up @@ -445,3 +448,9 @@ export const MonadAsyncEitherSequential: Monad<KindAsyncEither> = {
join,
chain,
};

export const Do = <A>() => of<A>(<A> {});

export const bind = bind_(MonadAsyncEitherSequential);

export const bindTo = bindTo_(MonadAsyncEitherSequential);
52 changes: 52 additions & 0 deletions chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,55 @@ export interface Chain<U extends Kind> extends TypeClass<U>, Apply<U> {
ta: $<U, [A, B, C], [D], [E]>,
) => $<U, [I, B | J, C | K], [D], [E]>;
}

/**
* ChainRec
* https://github.com/fantasyland/static-land/blob/master/docs/spec.md#chain
*/
export interface ChainRec<U extends Kind> extends TypeClass<U>, Chain<U> {
readonly chainRec: <A, B, I, J = never, K = never, D = unknown, E = unknown>(
fati: (a: A) => $<U, [I, J, K], [D], [E]>,
a: A,
) => $<U, [B, J, K], [D], [E]>;
}

// chainFirst
export function chainFirst<U extends Kind>(
M: Chain<U>,
): <A, I, J = never, K = never, D = unknown, E = unknown>(
f: (a: A) => $<U, [I, J, K], [D], [E]>,
) => <B, C>(ma: $<U, [A, B, C], [D], [E]>) => $<U, [A, B, C], [D], [E]> {
// @ts-expect-error <type-errors> TODO: @baetheus
return (f) => (ma) => M.chain((a) => M.map(() => a)(f(a)))(ma);
}

// bind
export function bind<U extends Kind>(
M: Chain<U>,
): <
N extends string,
A,
B,
C,
I,
J = never,
K = never,
D = unknown,
E = unknown,
>(
name: Exclude<N, keyof A>,
f: (a: A) => $<U, [I, J, K], [D], [E]>,
) => <B, C>(
ma: $<U, [A, B, C], [D], [E]>,
) => $<
U,
[{ readonly [K in keyof A | N]: K extends keyof A ? A[K] : I }],
[D],
[E]
> {
return (name, f) =>
// @ts-expect-error <type-errors> TODO: @baetheus
M.chain((a) =>
M.map((b) => Object.assign({}, a, { [name]: b }) as any)(f(a))
);
}
93 changes: 47 additions & 46 deletions datum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -52,11 +54,11 @@ export const initial: Initial = { tag: "Initial" };
export const pending: Pending = { tag: "Pending" };

export function refresh<D>(value: D): Datum<D> {
return ({ tag: "Refresh", value });
return { tag: "Refresh", value };
}

export function replete<D>(value: D): Datum<D> {
return ({ tag: "Replete", value });
return { tag: "Replete", value };
}

export function constInitial<A = never>(): Datum<A> {
Expand All @@ -80,15 +82,7 @@ export function tryCatch<A>(fa: () => A): Datum<A> {
}

export function toLoading<A>(ta: Datum<A>): Datum<A> {
return pipe(
ta,
match(
constPending,
constPending,
refresh,
refresh,
),
);
return pipe(ta, match(constPending, constPending, refresh, refresh));
}

export function isInitial<A>(ta: Datum<A>): ta is Initial {
Expand Down Expand Up @@ -160,12 +154,10 @@ export function map<A, I>(fai: (a: A) => I): (ta: Datum<A>) => Datum<I> {
);
}

export function ap<A>(
ua: Datum<A>,
): <I>(ufai: Datum<(a: A) => I>) => Datum<I> {
export function ap<A>(ua: Datum<A>): <I>(ufai: Datum<(a: A) => I>) => Datum<I> {
switch (ua.tag) {
case "Initial":
return (ufai) => isLoading(ufai) ? pending : initial;
return (ufai) => (isLoading(ufai) ? pending : initial);
case "Pending":
return constPending;
case "Replete":
Expand All @@ -178,34 +170,29 @@ export function ap<A>(
? 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<A, I>(
fati: (a: A) => Datum<I>,
): (ta: Datum<A>) => Datum<I> {
return match(
constInitial,
constPending,
fati,
flow(fati, toLoading),
);
return match(constInitial, constPending, fati, flow(fati, toLoading));
}

export function join<A>(taa: Datum<Datum<A>>): Datum<A> {
return pipe(taa, chain(identity));
}

export function alt<A>(tb: Datum<A>): (ta: Datum<A>) => Datum<A> {
return (ta) => isSome(ta) ? ta : tb;
return (ta) => (isSome(ta) ? ta : tb);
}

export function reduce<A, O>(
foao: (o: O, a: A) => O,
o: O,
): (ta: Datum<A>) => O {
return (ta) => isSome(ta) ? foao(o, ta.value) : o;
return (ta) => (isSome(ta) ? foao(o, ta.value) : o);
}

export function traverse<V extends Kind>(
Expand All @@ -217,70 +204,78 @@ export function traverse<V extends Kind>(
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<A>({ show }: Show<A>): Show<Datum<A>> {
return ({
return {
show: match(
() => `Initial`,
() => `Pending`,
(a) => `Replete(${show(a)})`,
(a) => `Refresh(${show(a)})`,
),
});
};
}

export function getSemigroup<A>(
S: Semigroup<A>,
): Semigroup<Datum<A>> {
return ({
export function getSemigroup<A>(S: Semigroup<A>): Semigroup<Datum<A>> {
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<A>(S: Semigroup<A>): Monoid<Datum<A>> {
return ({
return {
...getSemigroup(S),
empty: constInitial,
});
};
}

export function getEq<A>(S: Eq<A>): Eq<Datum<A>> {
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<A>(O: Ord<A>): Ord<Datum<A>> {
return fromCompare((fst, snd) =>
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),
),
)
);
Expand All @@ -295,3 +290,9 @@ export const TraversableDatum: Traversable<KindDatum> = {
reduce,
traverse,
};

export const Do = <A>() => of<A>(<A> {});

export const bind = bind_(MonadDatum);

export const bindTo = bindTo_(MonadDatum);
Loading