Skip to content

Instantly share code, notes, and snippets.

@baetheus
Last active August 31, 2022 22:55
Show Gist options
  • Save baetheus/0572023bed2d20e3364a70bd6b62dd30 to your computer and use it in GitHub Desktop.
Save baetheus/0572023bed2d20e3364a70bd6b62dd30 to your computer and use it in GitHub Desktop.
Working general compose with narrowing "never" on incorrect usage
// deno-fmt-ignore-file no-explicit-any
import type { Iso } from "./iso.ts";
import type { Lens } from "./lens.ts";
import type { Prism } from "./prism.ts";
import type { Optional } from "./optional.ts";
import type { Traversal } from "./traversal.ts";
import * as I from "./iso.ts";
import * as L from "./lens.ts";
import * as P from "./prism.ts";
import * as O from "./optional.ts";
import * as T from "./traversal.ts";
import { pipe } from "./fns.ts";
export type Optic<A, B> =
| Iso<A, B>
| Lens<A, B>
| Prism<A, B>
| Optional<A, B>
| Traversal<A, B>;
// deno-fmt-ignore
// deno-lint-ignore no-explicit-any
export type Compose<Left extends Optic<any, any>, Right extends Optic<any, any>> =
// Iso
Left extends Iso<infer A, infer B>
? Right extends Iso<B, infer C> ? Iso<A, C>
: Right extends Lens<B, infer C> ? Lens<A, C>
: Right extends Prism<B, infer C> ? Prism<A, C>
: Right extends Optional<B, infer C> ? Optional<A, C>
: Right extends Traversal<B, infer C> ? Traversal<A, C>
: never
: Left extends Lens<infer A, infer B>
? Right extends Iso<B, infer C> ? Lens<A, C>
: Right extends Lens<B, infer C> ? Lens<A, C>
: Right extends Prism<B, infer C> ? Optional<A, C>
: Right extends Optional<B, infer C> ? Optional<A, C>
: Right extends Traversal<B, infer C> ? Traversal<A, C>
: never
: Left extends Prism<infer A, infer B>
? Right extends Iso<B, infer C> ? Prism<A, C>
: Right extends Lens<B, infer C> ? Optional<A, C>
: Right extends Prism<B, infer C> ? Prism<A, C>
: Right extends Optional<B, infer C> ? Optional<A, C>
: Right extends Traversal<B, infer C> ? Traversal<A, C>
: never
: Left extends Optional<infer A, infer B>
? Right extends Iso<B, infer C> ? Optional<A, C>
: Right extends Lens<B, infer C> ? Optional<A, C>
: Right extends Prism<B, infer C> ? Optional<A, C>
: Right extends Optional<B, infer C> ? Optional<A, C>
: Right extends Traversal<B, infer C> ? Traversal<A, C>
: never
: Left extends Traversal<infer A, infer B>
? Right extends Iso<B, infer C> ? Traversal<A, C>
: Right extends Lens<B, infer C> ? Traversal<A, C>
: Right extends Prism<B, infer C> ? Traversal<A, C>
: Right extends Optional<B, infer C> ? Traversal<A, C>
: Right extends Traversal<B, infer C> ? Traversal<A, C>
: never
: never;
// deno-lint-ignore no-explicit-any
type NarrowLeft<R> = R extends Optic<infer B, infer _> ? Optic<any, B> : never;
// deno-lint-ignore no-explicit-any
export function compose<R extends Optic<any, any>>(right: R): <L extends NarrowLeft<R>>(left: L) => Compose<L, R> {
return <L extends NarrowLeft<R>>(left: L): Compose<L, R>=> {
switch (left.tag) {
case "Iso":
switch (right.tag) {
case "Iso": return I.compose(right)(left) as Compose<L, R>;
case "Lens": return I.composeLens(right)(left) as Compose<L, R>;
case "Prism": return I.composePrism(right)(left) as Compose<L, R>;
case "Optional": return I.composeOptional(right)(left) as Compose<L, R>;
case "Traversal": return I.composeTraversal(right)(left) as Compose<L, R>;
}
break;
case "Lens":
switch (right.tag) {
case "Iso": return L.composeIso(right)(left) as Compose<L, R>;
case "Lens": return L.compose(right)(left) as Compose<L, R>;
case "Prism": return L.composePrism(right)(left) as Compose<L, R>;
case "Optional": return L.composeOptional(right)(left) as Compose<L, R>;
case "Traversal": return L.composeTraversal(right)(left) as Compose<L, R>;
}
break;
case "Prism":
switch (right.tag) {
case "Iso": return P.composeIso(right)(left) as Compose<L, R>;
case "Lens": return P.composeLens(right)(left) as Compose<L, R>;
case "Prism": return P.compose(right)(left) as Compose<L, R>;
case "Optional": return P.composeOptional(right)(left) as Compose<L, R>;
case "Traversal": return P.composeTraversal(right)(left) as Compose<L, R>;
}
break;
case "Optional":
switch (right.tag) {
case "Iso": return O.composeIso(right)(left) as Compose<L, R>;
case "Lens": return O.composeLens(right)(left) as Compose<L, R>;
case "Prism": return O.composePrism(right)(left) as Compose<L, R>;
case "Optional": return O.compose(right)(left) as Compose<L, R>;
case "Traversal": return O.composeTraversal(right)(left) as Compose<L, R>;
}
break;
case "Traversal":
switch (right.tag) {
case "Iso": return T.composeIso(right)(left) as Compose<L, R>;
case "Lens": return T.composeLens(right)(left) as Compose<L, R>;
case "Prism": return T.composePrism(right)(left) as Compose<L, R>;
case "Optional": return T.composeOptional(right)(left) as Compose<L, R>;
case "Traversal": return T.compose(right)(left) as Compose<L, R>;
}
break;
}
};
}
// TEST
type TEST = {
one: number,
two: {
three: string,
}
};
const test: TEST = {
one: 1,
two: { three: "three" }
}
const TT: Iso<TEST, [number, string]> = I.iso(
({ one, two: { three }}) => [one, three],
([one, three]) => ({ one, two: { three }})
);
const tt = I.reverse(TT);
const two = pipe(
L.id<TEST>(),
L.prop("two"),
)
const three = pipe(
P.id<TEST["two"]>(),
P.prop("three"),
);
// Optional<TEST, string>
export const c = pipe(
TT,
compose(tt),
compose(TT),
compose(tt),
compose(two),
compose(three)
)
console.log(c.getOption(test));
console.log(c.set("THREE")(test))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment