Skip to content
This repository has been archived by the owner on May 3, 2021. It is now read-only.

Commit

Permalink
feat: implement task either adt
Browse files Browse the repository at this point in the history
  • Loading branch information
baetheus committed Sep 20, 2020
1 parent a68076a commit 36aa339
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 0 deletions.
69 changes: 69 additions & 0 deletions task_either.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type * as TC from "./type_classes.ts";
import type { $, _0, _1, _2, _3 } from "./hkts.ts";

import { isNotNil, Lazy, Predicate, Refinement } from "./fns.ts";
import * as E from "./either.ts";
import * as T from "./task.ts";
import * as S from "./sequence.ts";
import * as D from "./derivations.ts";

/***************************************************************************************************
* @section Types
**************************************************************************************************/

export type TaskEither<L, R> = T.Task<E.Either<L, R>>;

/***************************************************************************************************
* @section Constructors
**************************************************************************************************/

export const left = <E = never, A = never>(left: E): TaskEither<E, A> =>
T.of(E.left(left));
export const right = <E = never, A = never>(right: A): TaskEither<E, A> =>
T.of(E.right(right));

export const tryCatch = <E, A>(
f: Lazy<A>,
onError: (e: unknown) => E,
): TaskEither<E, A> => {
try {
return right(f());
} catch (e) {
return left(onError(e));
}
};

/***************************************************************************************************
* @section Modules
**************************************************************************************************/

export const Monad = E.getEitherM(T.Monad);

export const Functor: TC.Functor<TaskEither<_0, _1>, 2> = {
map: Monad.map,
};

export const Applicative: TC.Applicative<TaskEither<_0, _1>, 2> = {
of: Monad.of,
ap: Monad.ap,
map: Monad.map,
};

export const Apply: TC.Apply<TaskEither<_0, _1>, 2> = {
ap: Monad.ap,
map: Functor.map,
};

/***************************************************************************************************
* @section Pipeables
**************************************************************************************************/

export const { of, ap, map, join, chain } = D.createPipeableMonad(Monad);

/***************************************************************************************************
* @section Sequence
**************************************************************************************************/

export const sequenceTuple = S.createSequenceTuple(Apply);

export const sequenceStruct = S.createSequenceStruct(Apply);
152 changes: 152 additions & 0 deletions testing/task_either.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

import * as T from "../task_either.ts";
import * as E from "../either.ts";

Deno.test({
name: "TaskEither Constructors",
async fn(): Promise<void> {
assertEquals(await T.of(1)(), E.right(1));
},
});

Deno.test({
name: "TaskEither Instances",
async fn(): Promise<void> {
// Test Laws
const famb = (
n: number,
) => (n < 0
? T.Monad.of<number, number>(0)
: T.Monad.of<number, number>(n));
const fbmc = (n: number) => T.Monad.of<number, string>(n.toString());

// Monad Left Identity: M.chain(f, M.of(a)) ≡ f(a)
assertEquals(
await T.Monad.chain(famb, T.Monad.of(1))(),
await famb(1)(),
`TaskEither : Monad Left Identity`,
);

// Monad Right Identity: M.chain(M.of, u) ≡ u
assertEquals(
await T.Monad.chain<number, number, number>(T.Monad.of, T.Monad.of(1))(),
await T.Monad.of(1)(),
`TaskEither : Monad Right Identity`,
);

// Monad Associativity: M.chain(b => Mc, M.chain(a => Mb, Ma)) === M.chain(a => M.chain(b => Mc, (a => Mb)(a)), Ma)
assertEquals(
await T.Monad.chain<number, number, string>(
fbmc,
T.Monad.chain<number, number, number>(famb, T.Monad.of(1)),
)(),
await T.Monad.chain<number, number, string>(
(a) => T.Monad.chain<number, number, string>(fbmc, famb(a)),
T.Monad.of(1),
)(),
`TaskEither : Monad Associativity 1`,
);

assertEquals(
T.Monad.chain(
fbmc,
T.Monad.chain<number, number, number>(famb, T.Monad.of(-1)),
)(),
T.Monad.chain<number, number, string>(
(a) => T.Monad.chain<number, number, string>(fbmc, famb(a)),
T.Monad.of(-1),
)(),
`TaskEither : Monad Associativity 2`,
);

const fatb = (n: number) => T.Monad.of<number, number>(n + 1);
const fbtc = (n: number) => T.Monad.of<number, string>(n.toString());

// Chain Associativity: M.chain(g, M.chain(f, u)) ≡ M.chain(x => M.chain(g, f(x)), u)
assertEquals(
await T.Monad.chain(
fbtc,
T.Monad.chain<number, number, number>(fatb, T.Monad.of(1)),
)(),
await T.Monad.chain<number, number, string>(
(x) => T.Monad.chain<number, number, string>(fbtc, fatb(x)),
T.Monad.of(1),
)(),
`TaskEither : Chain Associativity`,
);

const fab = (n: number) => n + 1;
const fbc = (n: number): string => n.toString();
const fgab = (f: typeof fbc) => (g: typeof fab) => (n: number) => f(g(n));

// Apply Composition: A.ap(A.ap(A.map(f => g => x => f(g(x)), a), u), v) ≡ A.ap(a, A.ap(u, v))
assertEquals(
await T.Apply.ap(
T.Monad.ap<any, any, any>(
T.Monad.map(fgab, T.Monad.of(fbc)),
T.Monad.of(fab),
),
T.Monad.of(1),
)(),
await T.Monad.ap<number, number, string>(
T.Monad.of(fbc),
T.Monad.ap(T.Monad.of<any, any>(fab), T.Monad.of(1)),
)(),
`TaskEither : Apply Composition`,
);

// Functor Identity: F.map(x => x, a) ≡ a
assertEquals(
await T.Applicative.map((n: number) => n, T.Applicative.of(1))(),
await T.Applicative.of(1)(),
`TaskEither : Functor Identity`,
);

// Functor Composition: F.map(x => f(g(x)), a) ≡ F.map(f, F.map(g, a))
assertEquals(
await T.Applicative.map(
(x: number) => fbc(fab(x)),
T.Applicative.of(1),
)(),
await T.Applicative.map(
fbc,
T.Applicative.map(fab, T.Applicative.of(1)),
)(),
`TaskEither : Functor Composition`,
);

// Applicative Identity: A.ap(A.of(x => x), v) ≡ v
assertEquals(
await T.Applicative.ap<number, number, number>(
T.Applicative.of((n: number): number => n),
T.Applicative.of(1),
)(),
await T.Applicative.of(1)(),
`TaskEither : Applicative Identity`,
);

// Applicative Homomorphism: M.ap(A.of(f), A.of(x)) ≡ A.of(f(x))
assertEquals(
await T.Applicative.ap<number, number, number>(
T.Applicative.of(fab),
T.Applicative.of(1),
)(),
await T.Applicative.of(fab(1))(),
`TaskEither : Applicative Homomorphism`,
);

// Applicative Interchange: A.ap(u, A.of(y)) ≡ A.ap(A.of(f => f(y)), u)
assertEquals(
await T.Applicative.ap<number, number, number>(
T.Applicative.of(fab),
T.Applicative.of(2),
)(),
await T.Applicative.ap<number, number, number>(
T.Applicative.of<any, any>((f: typeof fab) => f(2)),
T.Applicative.of<any, any>(fab),
)(),
`TaskEither : Applicative Interchange`,
);
},
});

0 comments on commit 36aa339

Please sign in to comment.