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

Commit

Permalink
feat: implement traverse for all optics
Browse files Browse the repository at this point in the history
In order to implement traverse without merging all work into
an "internal" file or cause cyclic import problems I needed to
hoist fromTraverable to the derivations.ts file. It's starting
to make sense why gcanti did most work in the internal file, but
I'm confident that implementing all of the composition functions
as well as the converters and pipeable was worth it for learning
purposes.
  • Loading branch information
baetheus committed Nov 25, 2020
1 parent 11d6355 commit 0269f89
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 58 deletions.
85 changes: 46 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,56 +28,63 @@ A full featured pipeable optics library:

```ts
import * as A from "https://deno.land/x/hkts/array.ts";
import * as L from "https://deno.land/x/hkts/lens.ts";
import * as T from "https://deno.land/x/hkts/traversal.ts";
import * as P from "https://deno.land/x/hkts/prism.ts";
import { none, Option, some } from "https://deno.land/x/hkts/option.ts";
import { pipe } from "https://deno.land/x/hkts/fns.ts";
import * as O from "https://deno.land/x/hkts/option.ts";
import { flow, pipe } from "https://deno.land/x/hkts/fns.ts";

const capitalizeWord = (str: string) =>
str.substr(0, 1).toUpperCase() + str.substr(1);

const split = (char: string) => (str: string) => str.split(char);

interface User {
username: Option<string>;
ready: boolean;
}
const join = (char: string) => (arr: readonly string[]) => arr.join(char);

type Users = readonly User[];
const capitalizeWords = flow(split(" "), A.map(capitalizeWord), join(" "));

const capitalize = (s: string): string =>
s.substring(0, 1).toUpperCase() + s.substring(1);
type User = {
name: string;
addresses: {
street: string;
city: string;
state: string;
zip: string;
}[];
};

const getArrayTraversal = T.fromTraversable(A.Traversable);
type Users = readonly O.Option<User>[];

const capitalizeUsernames = pipe(
T.id<Users>(),
T.compose(getArrayTraversal()),
T.prop("username"),
T.composePrism(P.some()),
T.modify(capitalize)
const capitalizeCities = pipe(
L.id<Users>(),
L.traverse(A.Traversable),
T.traverse(O.Traversable),
T.prop("addresses"),
T.traverse(A.Traversable),
T.prop("city"),
T.modify(capitalizeWords)
);

const users: Users = [
{ username: some("brandon"), ready: true },
{ username: some("breonna"), ready: true },
{ username: some("george"), ready: true },
{ username: none, ready: false },
O.some({
name: "breonna",
addresses: [
{ street: "123 Main St", city: "davis", state: "California", zip: "00000" },
{ street: "777 Jones Ave", city: "sacramento", state: "California", zip: "00000" },
{ street: "881 Second St", city: "austin", state: "Texas", zip: "00000" },
],
}),
O.none,
O.some({
name: "george",
addresses: [
{ street: "123 Main St", city: "los angeles", state: "California", zip: "00000" },
{ street: "777 Jones Ave", city: "jonesborough", state: "Tennessee", zip: "00000" },
{ street: "881 Second St", city: "san francisco", state: "California", zip: "00000" },
],
}),
];

console.log({
before: users,
after: capitalizeUsernames(users),
});
// {
// before: [
// { username: { tag: "Some", value: "brandon" }, ready: true },
// { username: { tag: "Some", value: "breonna" }, ready: true },
// { username: { tag: "Some", value: "george" }, ready: true },
// { username: { tag: "None" }, ready: false }
// ],
// after: [
// { username: { tag: "Some", value: "Brandon" }, ready: true },
// { username: { tag: "Some", value: "Breonna" }, ready: true },
// { username: { tag: "Some", value: "George" }, ready: true },
// { username: { tag: "None" }, ready: false }
// ]
// }
const capitalizedUserCities = capitalizeCities(users);
```

## Conventions
Expand Down
20 changes: 20 additions & 0 deletions derivations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type * as TC from "./type_classes.ts";
import type { $ } from "./types.ts";
import type { Traversal } from "./traversal.ts";

import { identity } from "./fns.ts";

Expand Down Expand Up @@ -152,3 +153,22 @@ export const createPipeableBifunctor: CreatePipeableBifunctor = <T>({
bimap: (fab, fcd) => (tac) => bimap(fab, fcd, tac),
mapLeft: (fef) => (tac) => mapLeft(fef, tac),
});

/**
* Derive Traversal from Traversable
*/

// deno-fmt-ignore
type FromTraversableFn = {
<T, L extends 1>(T: TC.Traversable<T, L>): <A>() => Traversal<$<T, [A]>, A>;
<T, L extends 2>(T: TC.Traversable<T, L>): <E, A>() => Traversal<$<T, [E, A]>, A>;
<T, L extends 3>(T: TC.Traversable<T, L>): <R, E, A>() => Traversal<$<T, [R, E, A]>, A>;
<T, L extends 4>(T: TC.Traversable<T, L>): <S, R, E, A>() => Traversal<$<T, [S, R, E, A]>, A>;
};

export const createTraversal: FromTraversableFn = <T>(T: TC.Traversable<T>) =>
<A>(): Traversal<$<T, [A]>, A> => ({
getModify: <U>(A: TC.Applicative<U>) => {
return (f: (a: A) => $<U, [A]>) => (s: $<T, [A]>) => T.traverse(A, f, s);
},
});
5 changes: 5 additions & 0 deletions iso.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Traversal } from "./traversal.ts";

import * as O from "./option.ts";
import * as E from "./either.ts";
import { createTraversal } from "./derivations.ts";
import { constant, flow, identity } from "./fns.ts";

/***************************************************************************************************
Expand Down Expand Up @@ -132,6 +133,10 @@ export const reverse = <S, A>(sa: Iso<S, A>): Iso<A, S> => ({
reverseGet: sa.get,
});

export const traverse = <T>(T: TC.Traversable<T>) =>
<S, A>(sa: Iso<S, $<T, [A]>>): Traversal<S, A> =>
composeTraversal(createTraversal(T)<A>())(sa);

/***************************************************************************************************
* @section Pipeable Over ADT
**************************************************************************************************/
Expand Down
5 changes: 5 additions & 0 deletions lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Optional } from "./optional.ts";
import * as O from "./option.ts";
import * as E from "./either.ts";
import * as R from "./record.ts";
import { createTraversal } from "./derivations.ts";
import { constant, flow, identity, pipe } from "./fns.ts";

import { atRecord } from "./at.ts";
Expand Down Expand Up @@ -151,6 +152,10 @@ export const modify = <A>(f: (a: A) => A) =>
return o === n ? s : sa.set(n)(s);
};

export const traverse = <T>(T: TC.Traversable<T>) =>
<S, A>(sa: Lens<S, $<T, [A]>>): Traversal<S, A> =>
composeTraversal(createTraversal(T)<A>())(sa);

export const prop = <A, P extends keyof A>(
prop: P,
) =>
Expand Down
5 changes: 5 additions & 0 deletions prism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Traversal } from "./traversal.ts";

import * as O from "./option.ts";
import * as E from "./either.ts";
import { createTraversal } from "./derivations.ts";
import { flow, identity, pipe } from "./fns.ts";

import { atRecord } from "./at.ts";
Expand Down Expand Up @@ -166,6 +167,10 @@ export const filter: FilterFn = <A>(predicate: Predicate<A>) =>
reverseGet: sa.reverseGet,
});

export const traverse = <T>(T: TC.Traversable<T>) =>
<S, A>(sa: Prism<S, $<T, [A]>>): Traversal<S, A> =>
composeTraversal(createTraversal(T)<A>())(sa);

export const getModifyOption = <S, A>(sa: Prism<S, A>) =>
(faa: (a: A) => A) =>
(s: S): O.Option<S> =>
Expand Down
24 changes: 5 additions & 19 deletions traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Option } from "./option.ts";
import * as I from "./identity.ts";
import * as O from "./option.ts";
import * as E from "./either.ts";
import { createTraversal } from "./derivations.ts";
import { flow, identity, pipe } from "./fns.ts";

import { atRecord } from "./at.ts";
Expand Down Expand Up @@ -48,20 +49,7 @@ export const id = <S>(): Traversal<S, S> => ({
getModify: <T>(_: TC.Applicative<T, any>) => <A>(f: (a: A) => $<T, [A]>) => f,
});

// deno-fmt-ignore
type FromTraversableFn = {
<T, L extends 1>(T: TC.Traversable<T, L>): <A>() => Traversal<$<T, [A]>, A>;
<T, L extends 2>(T: TC.Traversable<T, L>): <E, A>() => Traversal<$<T, [E, A]>, A>;
<T, L extends 3>(T: TC.Traversable<T, L>): <R, E, A>() => Traversal<$<T, [R, E, A]>, A>;
<T, L extends 4>(T: TC.Traversable<T, L>): <S, R, E, A>() => Traversal<$<T, [S, R, E, A]>, A>;
};

export const fromTraversable: FromTraversableFn = <T>(T: TC.Traversable<T>) =>
<A>(): Traversal<$<T, [A]>, A> => ({
getModify: <U>(A: TC.Applicative<U>) => {
return (f: (a: A) => $<U, [A]>) => (s: $<T, [A]>) => T.traverse(A, f, s);
},
});
export const fromTraversable = createTraversal;

/***************************************************************************************************
* @section Modules
Expand Down Expand Up @@ -157,11 +145,9 @@ type TraverseFn = {
<T, L extends 4>(T: TC.Traversable<T, L>): <S, Q, R, E, A>(sta: Traversal<S, $<T, [Q, R, E, A]>>) => Traversal<S, A>
};

export const traverse: TraverseFn = <T>(
M: TC.Traversable<T>,
) =>
<S, A>(sta: Traversal<S, $<T, [A]>>): Traversal<S, A> =>
compose(fromTraversable(M)<A>())(sta);
export const traverse = <T>(T: TC.Traversable<T>) =>
<S, A>(sa: Traversal<S, $<T, [A]>>): Traversal<S, A> =>
compose(createTraversal(T)<A>())(sa);

/***************************************************************************************************
* @section Pipeable Over ADT
Expand Down

0 comments on commit 0269f89

Please sign in to comment.