Skip to content

Commit

Permalink
feat!: Add natural transformations and profunctors (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina authored Jan 19, 2023
1 parent 524b5c3 commit cfc1652
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 1 deletion.
44 changes: 44 additions & 0 deletions src/coyoneda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Get1, Hkt1, Hkt3 } from "./hkt.js";
import { compose, id } from "./func.js";

import type { Contravariant } from "./type-class/variance.js";

export interface Coyoneda<F, B, A> {
readonly hom: (a: A) => B;
readonly map: Get1<F, B>;
}
export interface CoyonedaConstructor<A> {
<B>(hom: (a: A) => B): <F>(map: Get1<F, B>) => Coyoneda<F, B, A>;
}
export const coyoneda =
<A>(): CoyonedaConstructor<A> =>
<B>(hom: (a: A) => B) =>
<F>(map: Get1<F, B>): Coyoneda<F, B, A> => ({
hom,
map,
});

export const lift = <F, A>(fa: Get1<F, A>): Coyoneda<F, A, A> => coyoneda<A>()(id)(fa);

export const lower =
<F extends Hkt1>(contra: Contravariant<F>) =>
<B, A>(coy: Coyoneda<F, B, A>): Get1<F, A> =>
contra.contraMap(coy.hom)(coy.map);

export const hoist =
<F, G>(nat: <A>(fa: Get1<F, A>) => Get1<G, A>) =>
<B, A>(coy: Coyoneda<F, B, A>): Coyoneda<G, B, A> =>
coyoneda<A>()(coy.hom)(nat(coy.map));

export const contraMap =
<T, U>(fn: (t: T) => U) =>
<F, B>(coy: Coyoneda<F, B, U>): Coyoneda<F, B, T> => {
const { hom, map } = coy;
return { hom: compose(hom)(fn), map };
};

export interface CoyonedaHkt extends Hkt3 {
readonly type: Coyoneda<this["arg3"], this["arg2"], this["arg1"]>;
}

export const contravariant: Contravariant<CoyonedaHkt> = { contraMap };
14 changes: 14 additions & 0 deletions src/identity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { flip, id } from "./func.js";

import type { Distributive } from "./type-class/distributive.js";
import type { Hkt1 } from "./hkt.js";
import type { Monad } from "./type-class/monad.js";
import type { Settable } from "./type-class/settable.js";
import type { Traversable } from "./type-class/traversable.js";
import { make } from "./tuple.js";

Expand All @@ -28,3 +30,15 @@ export const traversable: Traversable<IdentityHkt> = {
foldR: flip,
traverse: () => id,
};

export const distributive: Distributive<IdentityHkt> = {
map: id,
distribute: () => id,
};

export const settable: Settable<IdentityHkt> = {
...traversable,
...monad,
...distributive,
untainted: id,
};
27 changes: 27 additions & 0 deletions src/kleisli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Category3Monoid } from "./type-class/category.js";
import type { GetHktA1 } from "./hkt.js";
import type { Monad1 } from "./type-class/monad.js";

export interface Kleisli<M, A, B> {
readonly runKleisli: (a: A) => GetHktA1<M, B>;
}

declare const kleisliNominal: unique symbol;
export type KleisliHktKey = typeof kleisliNominal;

declare module "./hkt.js" {
interface HktDictA3<A1, A2, A3> {
[kleisliNominal]: Kleisli<A1, A2, A3>;
}
}

export const category = <M>(monad: Monad1<M>): Category3Monoid<KleisliHktKey, M> => ({
identity: <A>() => ({
runKleisli: monad.pure<A>,
}),
compose:
({ runKleisli: f }) =>
({ runKleisli: g }) => ({
runKleisli: (a) => monad.flatMap(g)(f(a)),
}),
});
124 changes: 124 additions & 0 deletions src/lens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { Get1, Get2, Hkt1, Hkt2 } from "./hkt.js";
import { Profunctor, fnPro, leftMap } from "./type-class/profunctor.js";
import { Settable, taintedDot, untaintedDot } from "./type-class/settable.js";

import type { Applicative } from "./type-class/applicative.js";
import type { Apply } from "./type-class/apply.js";
import type { Bifunctor } from "./type-class/bifunctor.js";
import type { Choice } from "./type-class/choice.js";
import type { Contravariant } from "./type-class/variance.js";
import type { Functor } from "./type-class/functor.js";
import type { IdentityHkt } from "./identity.js";

export type LensLike<F, S, T, A, B> = (outer: (a: A) => Get1<F, B>) => (s: S) => Get1<F, T>;
export type LensLikeSimple<F, S, A> = LensLike<F, S, S, A, A>;

export type Over<P, F, S, T, A, B> = (g: Get2<P, A, Get1<F, B>>) => (s: S) => Get1<F, T>;
export type OverSimple<P, F, S, A> = Over<P, F, S, S, A, A>;

export interface Lens<S, T, A, B> {
<F extends Hkt1>(functor: Functor<F>): LensLike<F, S, T, A, B>;
}
export type LensSimple<S, A> = Lens<S, S, A, A>;

export interface Traversal<S, T, A, B> {
<F extends Hkt1>(applicative: Applicative<F>): LensLike<F, S, T, A, B>;
}
export type TraversalSimple<S, A> = Traversal<S, S, A, A>;

export interface Traversal1<S, T, A, B> {
<F extends Hkt1>(applicative: Apply<F>): LensLike<F, S, T, A, B>;
}
export type Traversal1Simple<S, A> = Traversal1<S, S, A, A>;

export interface Setting<P, S, T, A, B> {
(g: Get2<P, A, Get1<IdentityHkt, B>>): (s: S) => Get1<IdentityHkt, T>;
}
export type SettingSimple<P, S, A> = Setting<P, S, S, A, A>;

export interface Setter<S, T, A, B> {
<F extends Hkt1>(settable: Settable<F>): LensLike<F, S, T, A, B>;
}
export type SetterSimple<S, A> = Setter<S, S, A, A>;

export interface Iso<S, T, A, B> {
<P extends Hkt2, F extends Hkt1>(pro: Profunctor<P>, functor: Functor<F>): (
g: Get2<P, A, Get1<F, B>>,
) => Get2<P, S, Get1<F, T>>;
}
export type IsoSimple<S, A> = Iso<S, S, A, A>;

export interface Review<T, B> {
<P extends Hkt2, F extends Hkt1>(
choice: Choice<P>,
bi: Bifunctor<P>,
settable: Settable<F>,
): OpticSimple<P, F, T, B>;
}

export interface Prism<S, T, A, B> {
<P extends Hkt2, F extends Hkt1>(choice: Choice<P>, app: Applicative<F>): (
g: Get2<P, A, Get1<F, B>>,
) => Get2<P, S, Get1<F, T>>;
}
export type PrismSimple<S, A> = Prism<S, S, A, A>;

export interface Equality<K1, K2, S extends K1, T extends K2, A extends K1, B extends K2> {
<K3, P, F>(setter: (k1: K1) => (k3: K3) => void, getter: (k2: K2) => K3): (
g: Get2<P, A, Get1<F, B>>,
) => Get2<P, S, Get1<F, T>>;
}
export type EqualitySimple<K, S extends K, A extends K> = Equality<K, K, S, S, A, A>;
export type As<K, A extends K> = EqualitySimple<K, A, A>;

export interface Getter<S, A> {
<F extends Hkt1>(contra: Contravariant<F>, functor: Functor<F>): (
g: (a: A) => Get1<F, A>,
) => (s: S) => Get1<F, S>;
}

export interface Fold<S, A> {
<F extends Hkt1>(contra: Contravariant<F>, app: Applicative<F>): (
g: (a: A) => Get1<F, A>,
) => (s: S) => Get1<F, S>;
}
export interface Fold1<S, A> {
<F extends Hkt1>(contra: Contravariant<F>, app: Apply<F>): (
g: (a: A) => Get1<F, A>,
) => (s: S) => Get1<F, S>;
}

export interface Optic<P, F, S, T, A, B> {
(g: Get2<P, A, Get1<F, B>>): Get2<P, S, Get1<F, T>>;
}
export type OpticSimple<P, F, S, A> = Optic<P, F, S, S, A, A>;

export interface Optical<P, Q, F, S, T, A, B> {
(g: Get2<P, A, Get1<F, B>>): Get2<Q, S, Get1<F, T>>;
}
export type OpticalSimple<P, Q, F, S, A> = Optical<P, Q, F, S, S, A, A>;

export const sets =
<P extends Hkt2, Q extends Hkt2, F extends Hkt1>(
proP: Profunctor<P>,
proQ: Profunctor<Q>,
settable: Settable<F>,
) =>
<S, T, A, B>(f: (pab: Get2<P, A, B>) => Get2<Q, S, T>): Optical<P, Q, F, S, T, A, B> =>
(g) =>
taintedDot(settable)(proQ)(f(untaintedDot(settable)(proP)(g)));

export const mapped =
<F extends Hkt1, A, B>(functor: Functor<F>): Setter<Get1<F, A>, Get1<F, B>, A, B> =>
(settable) =>
sets(fnPro, fnPro, settable)(functor.map);

export const contraMapped =
<F extends Hkt1, A, B>(contra: Contravariant<F>): Setter<Get1<F, B>, Get1<F, A>, A, B> =>
(settable) =>
sets(fnPro, fnPro, settable)(contra.contraMap);

export const argument =
<P extends Hkt2, A, B, R>(pro: Profunctor<P>): Setter<Get2<P, B, R>, Get2<P, A, R>, A, B> =>
(settable) =>
sets(fnPro, fnPro, settable)(leftMap(pro));
4 changes: 4 additions & 0 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ export * as Cat from "./cat.js";
export * as Cofree from "./cofree.js";
export * as Cont from "./cont.js";
export * as MonadCont from "./cont/monad.js";
export * as Coyoneda from "./coyoneda.js";
export * as Curry from "./curry.js";
export * as Free from "./free.js";
export * as MonadFree from "./free/monad.js";
export * as Frozen from "./frozen.js";
export * as Func from "./func.js";
export * as Hkt from "./hkt.js";
export * as Identity from "./identity.js";
export * as Kleisli from "./kleisli.js";
export * as Lazy from "./lazy.js";
export * as Lens from "./lens.js";
export * as List from "./list.js";
export * as Number from "./number.js";
export * as Option from "./option.js";
Expand All @@ -29,4 +32,5 @@ export * as Tuple from "./tuple.js";
export * as TypeClass from "./type-class.js";
export * as Writer from "./writer.js";
export * as MonadWriter from "./state/monad.js";
export * as Yoneda from "./yoneda.js";
export * as Zipper from "./zipper.js";
5 changes: 5 additions & 0 deletions src/type-class.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
export * as Applicative from "./type-class/applicative.js";
export * as Apply from "./type-class/apply.js";
export * as Bifunctor from "./type-class/bifunctor.js";
export * as Category from "./type-class/category.js";
export * as Choice from "./type-class/choice.js";
export * as Comonad from "./type-class/comonad.js";
export * as Distributive from "./type-class/distributive.js";
export * as Endo from "./type-class/endo.js";
export * as Eq from "./type-class/eq.js";
export * as FlatMap from "./type-class/flat-map.js";
export * as Foldable from "./type-class/foldable.js";
export * as Functor from "./type-class/functor.js";
export * as Monad from "./type-class/monad.js";
export * as Monoid from "./type-class/monoid.js";
export * as Nt from "./type-class/nt.js";
export * as Ord from "./type-class/ord.js";
export * as PartialEq from "./type-class/partial-eq.js";
export * as PartialOrd from "./type-class/partial-ord.js";
Expand All @@ -17,6 +21,7 @@ export * as Pure from "./type-class/pure.js";
export * as SemiGroup from "./type-class/semi-group.js";
export * as SemiGroupoid from "./type-class/semi-groupoid.js";
export * as SemiGroupal from "./type-class/semi-groupal.js";
export * as Settable from "./type-class/settable.js";
export * as Strong from "./type-class/strong.js";
export * as Traversable from "./type-class/traversable.js";
export * as Variance from "./type-class/variance.js";
27 changes: 27 additions & 0 deletions src/type-class/arrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Category, pipe } from "./category.js";
import type { Get2, Hkt2 } from "src/hkt.js";

import type { Tuple } from "../tuple.js";

export interface Arrow<A extends Hkt2> extends Category<A> {
readonly arr: <B, C>(fn: (b: B) => C) => Get2<A, B, C>;
readonly split: <B1, C1>(
arrow1: Get2<A, B1, C1>,
) => <B2, C2>(arrow2: Get2<A, B2, C2>) => Get2<A, Tuple<B1, B2>, Tuple<C1, C2>>;
}

export const first =
<A extends Hkt2>(a: Arrow<A>) =>
<B, C, D>(arrow: Get2<A, B, C>): Get2<A, Tuple<B, D>, Tuple<C, D>> =>
a.split(arrow)(a.identity<D>());

export const second =
<A extends Hkt2>(a: Arrow<A>) =>
<B, C, D>(arrow: Get2<A, B, C>): Get2<A, Tuple<D, B>, Tuple<D, C>> =>
a.split(a.identity<D>())(arrow);

export const fanOut =
<A extends Hkt2>(a: Arrow<A>) =>
<B, C1>(arrow1: Get2<A, B, C1>) =>
<C2>(arrow2: Get2<A, B, C2>): Get2<A, B, Tuple<C1, C2>> =>
pipe(a)(a.arr((b: B): Tuple<B, B> => [b, b]))(a.split(arrow1)(arrow2));
7 changes: 7 additions & 0 deletions src/type-class/bifunctor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Get2 } from "../hkt.js";

export interface Bifunctor<P> {
readonly biMap: <A, B>(
first: (a: A) => B,
) => <C, D>(second: (c: C) => D) => (curr: Get2<P, A, C>) => Get2<P, B, D>;
}
10 changes: 10 additions & 0 deletions src/type-class/category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ import type { SemiGroupoid } from "./semi-groupoid.js";
export interface Category<S extends Hkt2> extends SemiGroupoid<S> {
readonly identity: <A>() => Get2<S, A, A>;
}

export const compose = <S extends Hkt2>(
cat: Category<S>,
): (<B, C>(bc: Get2<S, B, C>) => <A>(ab: Get2<S, A, B>) => Get2<S, A, C>) => cat.compose;

export const pipe =
<S extends Hkt2>(cat: Category<S>) =>
<A, B>(bc: Get2<S, A, B>) =>
<C>(ab: Get2<S, B, C>): Get2<S, A, C> =>
cat.compose(ab)(bc);
9 changes: 9 additions & 0 deletions src/type-class/choice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Get2, Hkt2 } from "src/hkt.js";

import type { Profunctor } from "./profunctor.js";
import type { Result } from "src/result.js";

export interface Choice<P extends Hkt2> extends Profunctor<P> {
readonly left: <A, B, C>(curr: Get2<P, A, B>) => Get2<P, Result<A, C>, Result<B, C>>;
readonly right: <A, B, C>(curr: Get2<P, A, B>) => Get2<P, Result<C, A>, Result<C, B>>;
}
44 changes: 44 additions & 0 deletions src/type-class/distributive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Get1, Hkt1 } from "src/hkt.js";

import type { Functor } from "./functor.js";
import type { Monad } from "./monad.js";

export interface Distributive<G extends Hkt1> extends Functor<G> {
readonly distribute: <F extends Hkt1>(
functor: Functor<F>,
) => <A>(fga: Get1<F, Get1<G, A>>) => Get1<G, Get1<F, A>>;
}

export const collect =
<G extends Hkt1>(dist: Distributive<G>) =>
<F extends Hkt1>(functor: Functor<F>) =>
<A, B>(f: (a: A) => Get1<G, B>) =>
(fa: Get1<F, A>): Get1<G, Get1<F, B>> =>
dist.distribute(functor)(functor.map(f)(fa));

export const distributeM =
<G extends Hkt1>(dist: Distributive<G>) =>
<M extends Hkt1>(monad: Monad<M>) =>
<A>(mga: Get1<M, Get1<G, A>>): Get1<G, Get1<M, A>> =>
dist.distribute(monad)(mga);

export const collectM =
<G extends Hkt1>(dist: Distributive<G>) =>
<M extends Hkt1>(monad: Monad<M>) =>
<A, B>(f: (a: A) => Get1<G, B>) =>
(fa: Get1<M, A>): Get1<G, Get1<M, B>> =>
dist.distribute(monad)(monad.map(f)(fa));

export const contraverse =
<G extends Hkt1>(dist: Distributive<G>) =>
<F extends Hkt1>(functor: Functor<F>) =>
<A, B>(f: (fa: Get1<F, A>) => B) =>
(fga: Get1<F, Get1<G, A>>): Get1<G, B> =>
dist.map(f)(dist.distribute(functor)(fga));

export const coMapM =
<G extends Hkt1>(dist: Distributive<G>) =>
<M extends Hkt1>(monad: Monad<M>) =>
<A, B>(f: (ma: Get1<M, A>) => B) =>
(mga: Get1<M, Get1<G, A>>): Get1<G, B> =>
dist.map(f)(distributeM(dist)(monad)(mga));
Loading

0 comments on commit cfc1652

Please sign in to comment.