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

Commit

Permalink
feat: expand on either adt
Browse files Browse the repository at this point in the history
* either
  * add fromNullable
  * add tryCatch
  * add fromPredicate
  * add getOrElse
  * add swap
  * add orElse
  * add getShow
  * add getSemigroup
  * change typeclass import alias to TC from SL
  * add Bifunctor module
  * add pipeable reduce and traverse
  * add bimap
* move getShow to Modules section in option.ts
* add conventions and documentation section in README.md
* move port hyper-ts to pre-1.0.0 todos
* add createPipeableBifunctor helper
  • Loading branch information
baetheus committed Sep 12, 2020
1 parent d223b71 commit fad661f
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 17 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ const result = pipe(
);
// result = O.none.
```
## Conventions
This library focuses first on implementing [static-land](https://github.com/fantasyland/static-land) type classes for a given Algebraic Data Type (ie. Either or Option) first. These type class instances are then exported from the ADT's namespace (eg. `import { Monad } from 'https://deno.land/x/hkts/option.ts'`). With the exception of instance constructors (ie. getShow or getSemigroup) other ADT functions should all be pipeable. For functions that derive from type class modules, like `chain` or `map`, there are helpers in `type-classes.ts` that will generate the pipeable versions for you.
For good examples of the above conventions look at the `either.ts` or `option.ts`.
# Documentation
For the foreseeable future this library will not focus on documentation. Questions are welcome via [github issues](https://github.com/nullpub/hkts/issues) but I can't guaruntee speedy responses. Once a decent collection of ADTs and other utilities are ported and all the pre-1.0.0 todo items in `TODO.md` are complete I'll shift to documentation. Even then it's likely that I'll auto-generate the raw docs from exported function and statement types and will devote any time to building an example library that doubles as extra tests.
5 changes: 1 addition & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@
- port monocle-ts (likely will need additional adts for this)
- task and taskEither implementations
- investigate Functor/Functor2 overloads (instead of duplication)
- port hyper-ts to Deno

# Version 1.0.0 features

- auto documentation (port docs-ts)
- spend a week on examples and introduction posts

# Version 1.0.0 follow up

- port hyper-ts to Deno
76 changes: 68 additions & 8 deletions either.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as TC from "./type-classes.ts";
import { _0, _1 } from "./hkts.ts";
import * as SL from "./type-classes.ts";
import { isNotNil, Lazy, Predicate, Refinement } from "./fns.ts";

/***************************************************************************************************
* @section Types
Expand All @@ -16,6 +17,29 @@ export type Either<L, R> = Left<L> | Right<R>;
export const left = <L>(left: L): Left<L> => ({ tag: "Left", left });
export const right = <R>(right: R): Right<R> => ({ tag: "Right", right });

export function fromNullable<E>(e: E): <A>(a: A) => Either<E, NonNullable<A>> {
return <A>(a: A) => (isNotNil(a) ? right(a) : left(e));
}

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

export const fromPredicate: {
<E, A, B extends A>(refinement: Refinement<A, B>, onFalse: (a: A) => E): (
a: A
) => Either<E, B>;
<E, A>(predicate: Predicate<A>, onFalse: (a: A) => E): (a: A) => Either<E, A>;
} = <E, A>(predicate: Predicate<A>, onFalse: (a: A) => E) => (a: A) =>
predicate(a) ? right(a) : left(onFalse(a));

/***************************************************************************************************
* @section Destructors
**************************************************************************************************/
Expand All @@ -26,6 +50,20 @@ export const fold = <L, R, B>(
) => (ma: Either<L, R>): B =>
isLeft(ma) ? onLeft(ma.left) : onRight(ma.right);

export const getOrElse = <E, A>(onLeft: (e: E) => A) => (ma: Either<E, A>): A =>
isLeft(ma) ? onLeft(ma.left) : ma.right;

/***************************************************************************************************
* @section Combinators
**************************************************************************************************/

export const swap = <E, A>(ma: Either<E, A>): Either<A, E> =>
isLeft(ma) ? right(ma.left) : left(ma.right);

export const orElse = <E, A, M>(onLeft: (e: E) => Either<M, A>) => (
ma: Either<E, A>
): Either<M, A> => (isLeft(ma) ? onLeft(ma.left) : ma);

/***************************************************************************************************
* @section Guards
**************************************************************************************************/
Expand All @@ -38,37 +76,59 @@ export const isRight = <L, R>(m: Either<L, R>): m is Right<R> =>
* @section Modules
**************************************************************************************************/

export const Foldable: SL.Foldable2<Either<_0, _1>> = {
export const getShow = <E, A>(
Se: TC.Show<E>,
Sa: TC.Show<A>
): TC.Show<Either<E, A>> => ({
show: (ma) =>
isLeft(ma) ? `Left(${Se.show(ma.left)})` : `Right(${Sa.show(ma.right)})`,
});

export const getSemigroup = <E, A>(
S: TC.Semigroup<A>
): TC.Semigroup<Either<E, A>> => ({
concat: (x, y) =>
isLeft(y) ? x : isLeft(x) ? y : right(S.concat(x.right, y.right)),
});

export const Foldable: TC.Foldable2<Either<_0, _1>> = {
reduce: (faba, a, tb) => (isRight(tb) ? faba(a, tb.right) : a),
};

export const Monad = SL.createMonad2<Either<_0, _1>>({
export const Monad = TC.createMonad2<Either<_0, _1>>({
of: right,
chain: (fatb, ta) => (isRight(ta) ? fatb(ta.right) : ta),
});

export const Traversable: SL.Traversable2<Either<_0, _1>> = {
export const Traversable: TC.Traversable2<Either<_0, _1>> = {
map: Monad.map,
reduce: Foldable.reduce,
traverse: (F, faub, ta) =>
isLeft(ta) ? F.of(left(ta.left)) : F.map(right, faub(ta.right)),
};

export const Applicative: SL.Applicative2<Either<_0, _1>> = {
export const Applicative: TC.Applicative2<Either<_0, _1>> = {
of: Monad.of,
ap: Monad.ap,
map: Monad.map,
};

export const Apply: SL.Apply2<Either<_0, _1>> = {
export const Apply: TC.Apply2<Either<_0, _1>> = {
ap: Monad.ap,
map: Monad.map,
};

export const Bifunctor: TC.Bifunctor<Either<_0, _1>> = {
bimap: (fab, fcd, tac) =>
isLeft(tac) ? left(fab(tac.left)) : right(fcd(tac.right)),
};

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

export const { of, ap, map, join, chain } = SL.createPipeableMonad2(Monad);
export const { of, ap, map, join, chain } = TC.createPipeableMonad2(Monad);

export const { reduce, traverse } = TC.createPipeableTraversable2(Traversable);

export const { reduce, traverse } = SL.createPipeableTraversable2(Traversable);
export const { bimap } = TC.createPipeableBifunctor(Bifunctor);
10 changes: 5 additions & 5 deletions option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ export const none: None = { tag: "None" };
export const some = <A>(value: A): Option<A> => ({ tag: "Some", value });
export const constNone = () => none;

export const getShow = <A>({ show }: SL.Show<A>): SL.Show<Option<A>> => ({
show: (ma) => (isNone(ma) ? "None" : `${"Some"}(${show(ma.value)})`),
});

export const fromNullable = <A>(a: A): Option<NonNullable<A>> =>
isNotNil(a) ? some(a) : none;

Expand Down Expand Up @@ -70,9 +66,13 @@ export const isNone = <A>(m: Option<A>): m is None => m.tag === "None";
export const isSome = <A>(m: Option<A>): m is Some<A> => m.tag === "Some";

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

export const getShow = <A>({ show }: SL.Show<A>): SL.Show<Option<A>> => ({
show: (ma) => (isNone(ma) ? "None" : `${"Some"}(${show(ma.value)})`),
});

export const Monad = SL.createMonad<Option<_>>({
of: some,
chain: (fatb, ta) => (isSome(ta) ? fatb(ta.value) : ta),
Expand Down
11 changes: 11 additions & 0 deletions type-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,14 @@ export function createPipeableTraversable2<T>(
): Traversable2P<T> {
return createPipeableTraversable<T>(M as any) as Traversable2P<T>;
}

/**
* Derive BifunctorP from Bifunctor.
*/
export function createPipeableBifunctor<T>({
bimap,
}: Bifunctor<T>): BifunctorP<T> {
return {
bimap: (fab, fcd) => (tac) => bimap(fab, fcd, tac),
};
}

0 comments on commit fad661f

Please sign in to comment.