Skip to content

Commit

Permalink
feat: Add PromiseT monad transformer (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina authored Jan 2, 2024
1 parent 3bc014b commit 21714ca
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 1 deletion.
141 changes: 140 additions & 1 deletion src/promise.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,135 @@
import type { MonadCont } from "./cont/monad.ts";
import { absurd, id } from "./func.ts";
import type { Hkt1 } from "./hkt.ts";
import type { Apply2Only, Get1, Hkt1, Hkt2 } from "./hkt.ts";
import type { MonadPromise } from "./promise/monad.ts";
import type { Apply } from "./type-class/apply.ts";
import type { Applicative } from "./type-class/applicative.ts";
import { type FlatMap, flatten } from "./type-class/flat-map.ts";
import type { Functor } from "./type-class/functor.ts";
import type { Monad } from "./type-class/monad.ts";
import type { Monoid } from "./type-class/monoid.ts";
import { semiGroupSymbol } from "./type-class/semi-group.ts";
import type { SemiGroupal } from "./type-class/semi-groupal.ts";
import type { Traversable } from "./type-class/traversable.ts";

/**
* Monad transformer `PromiseT`, a generic form of `Promise`.
*/
export type PromiseT<M, A> = Promise<Get1<M, A>>;

/**
* Wraps the value of monad into a `Promise`. This is an alias of `Promise.resolve`.
*/
export const pureT: <M, A>(ma: Get1<M, A>) => PromiseT<M, A> = Promise.resolve;

/**
* Makes two `PromiseT`s into a `PromiseT` of tuple.
*
* @param semi - The instance for `SemiGroupal` of `M`.
* @param a - The left-side promise.
* @param b - The right-side promise.
* @returns The promise of product.
*/
export const productT =
<M>(semi: SemiGroupal<M>) =>
<A>(a: PromiseT<M, A>) =>
<B>(b: PromiseT<M, B>): PromiseT<M, [A, B]> =>
a.then((ma) => b.then(semi.product(ma)));

/**
* Maps item in a `PromiseT` by `fn`.
*
* @param f - The instance for `Functor` of `M`.
* @param fn - The function to transform `T` into `U`.
* @param t - The promise to be transformed.
* @returns The transformed promise.
*/
export const mapT =
<M>(f: Functor<M>) =>
<T, U>(fn: (t: T) => U) =>
(t: PromiseT<M, T>): PromiseT<M, U> => t.then(f.map(fn));

/**
* Maps and flatten an item of `PromiseT` by `fn`.
*
* @param m - The instance of `Traversable` and `FlatMap` for `M`.
* @param fn - A function to transform `T` into `PromiseT<M, U>`.
* @param t - A promise to be transformed.
* @returns The transformed promise.
*/
export const flatMapT =
<M>(m: Traversable<M> & FlatMap<M>) =>
<T, U>(fn: (t: T) => PromiseT<M, U>) =>
(t: PromiseT<M, T>): PromiseT<M, U> =>
t.then(m.traverse(applicative)(fn))
.then(flatten(m));

/**
* Applies the function in a `PromiseT`.
*
* @param fn - A `PromiseT` which contains function.
* @param t - A `PromiseT` to be transformed.
* @returns The applied `PromiseT`.
*/
export const applyT =
<M>(m: Apply<M>) =>
<T, U>(fn: PromiseT<M, (t: T) => U>) =>
(t: PromiseT<M, T>): PromiseT<M, U> =>
t.then((mt) => fn.then((mfn) => m.apply(mfn)(mt)));

export interface PromiseTHkt extends Hkt2 {
readonly type: PromiseT<this["arg2"], this["arg1"]>;
}

/**
* Returns the instance of `Monoid` about concatenating the promise computations.
*
* @param m - The instance of `Traversable` and `Monad` for `M`.
* @param identity - The identity of `T` that used as a default value.
* @returns The instance of `Monoid` for `PromiseT<M, T>`.
*/
export const monoidT =
<M>(m: Traversable<M> & Monad<M>) =>
<T>(identity: T): Monoid<PromiseT<M, T>> => ({
identity: Promise.resolve(m.pure(identity)),
combine: (l, r) => flatMapT(m)((mr: T) => mapT(m)(() => mr)(l))(r),
[semiGroupSymbol]: true,
});

/**
* @param m - The instance of `Functor` for `M`.
* @returns The instance of `Functor` for `PromiseT<M, _>`.
*/
export const functorT = <M>(
m: Functor<M>,
): Functor<Apply2Only<PromiseTHkt, M>> => ({
map: mapT(m),
});

/**
* @param m - The instance of `Functor` and `Apply` for `M`.
* @returns The instance of `Applicative` for `PromiseT<M, _>`.
*/
export const applicativeT = <M>(
m: Functor<M> & Apply<M>,
): Applicative<Apply2Only<PromiseTHkt, M>> => ({
pure,
map: mapT(m),
apply: applyT(m),
});

/**
* @param m - The instance of `Traversable`, `Apply` and `FlatMap` for `M`.
* @returns The instance of `Monad` for `PromiseT<M, _>`.
*/
export const monadT = <M>(
m: Traversable<M> & Apply<M> & FlatMap<M>,
): Monad<Apply2Only<PromiseTHkt, M>> => ({
pure,
map: mapT(m),
apply: applyT(m),
flatMap: flatMapT(m),
});

/**
* Wraps the value into `Promise`. This is the alias of `Promise.resolve`.
Expand Down Expand Up @@ -89,6 +214,20 @@ export const monoid = <T>(identity: T): Monoid<Promise<T>> => ({
[semiGroupSymbol]: true,
});

/**
* The instance of `Functor` for `Promise`.
*/
export const functor: Functor<PromiseHkt> = { map };

/**
* The instance of `Applicative` for `Promise`.
*/
export const applicative: Applicative<PromiseHkt> = {
pure,
map,
apply,
};

/**
* The instance of `Monad` for `Promise`.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/type-class/flat-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ export interface FlatMap<S> {
a: (t: T1) => Get1<S, U1>,
) => (t: Get1<S, T1>) => Get1<S, U1>;
}

export const flatten = <S>(
f: FlatMap<S>,
): <T>(t: Get1<S, Get1<S, T>>) => Get1<S, T> => f.flatMap((t) => t);

0 comments on commit 21714ca

Please sign in to comment.