An educational pure functional programming library in TypeScript
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 137 lines 6.5 kB view raw
1/** 2 * Type-safe optic composition. 3 * 4 * Composition table (outer \ inner → result): 5 * 6 * | outer \ inner | Lens | Prism | Optional | Traversal | 7 * |---------------|-----------|----------|----------|-----------| 8 * | Lens | Lens | Optional | Optional | Traversal | 9 * | Prism | Optional | Prism | Optional | Traversal | 10 * | Optional | Optional | Optional | Optional | Traversal | 11 * | Traversal | Traversal | Traversal| Traversal| Traversal | 12 * 13 * @module optics/compose 14 */ 15 16import { isSome, flatMapOption, mapOption } from "../prelude/option" 17import type { Lens, Optional, Prism, Traversal } from "./types" 18 19// ── Most specific overloads ────────────────────────────────────────────────── 20 21export function compose<S, A, B>(outer: Lens<S, A>, inner: Lens<A, B>): Lens<S, B> 22export function compose<S, A, B>(outer: Prism<S, A>, inner: Prism<A, B>): Prism<S, B> 23 24// ── Optional-yielding overloads ────────────────────────────────────────────── 25 26export function compose<S, A, B>(outer: Lens<S, A>, inner: Prism<A, B>): Optional<S, B> 27export function compose<S, A, B>(outer: Prism<S, A>, inner: Lens<A, B>): Optional<S, B> 28export function compose<S, A, B>(outer: Lens<S, A>, inner: Optional<A, B>): Optional<S, B> 29export function compose<S, A, B>(outer: Optional<S, A>, inner: Lens<A, B>): Optional<S, B> 30export function compose<S, A, B>(outer: Prism<S, A>, inner: Optional<A, B>): Optional<S, B> 31export function compose<S, A, B>(outer: Optional<S, A>, inner: Prism<A, B>): Optional<S, B> 32export function compose<S, A, B>(outer: Optional<S, A>, inner: Optional<A, B>): Optional<S, B> 33 34// ── Traversal-yielding overloads (any + Traversal, Traversal + any) ────────── 35 36export function compose<S, A, B>(outer: Lens<S, A>, inner: Traversal<A, B>): Traversal<S, B> 37export function compose<S, A, B>(outer: Prism<S, A>, inner: Traversal<A, B>): Traversal<S, B> 38export function compose<S, A, B>(outer: Optional<S, A>, inner: Traversal<A, B>): Traversal<S, B> 39export function compose<S, A, B>(outer: Traversal<S, A>, inner: Lens<A, B>): Traversal<S, B> 40export function compose<S, A, B>(outer: Traversal<S, A>, inner: Prism<A, B>): Traversal<S, B> 41export function compose<S, A, B>(outer: Traversal<S, A>, inner: Optional<A, B>): Traversal<S, B> 42export function compose<S, A, B>(outer: Traversal<S, A>, inner: Traversal<A, B>): Traversal<S, B> 43 44// ── Implementation ─────────────────────────────────────────────────────────── 45// eslint-disable-next-line @typescript-eslint/no-explicit-any 46export function compose(outer: any, inner: any): any { 47 const ot = outer._tag as string 48 const it = inner._tag as string 49 50 if (ot === "Lens" && it === "Lens") { 51 return { 52 _tag: "Lens", 53 get: (s: unknown) => inner.get(outer.get(s)), 54 set: (b: unknown) => (s: unknown) => outer.set(inner.set(b)(outer.get(s)))(s), 55 } 56 } 57 58 if (ot === "Prism" && it === "Prism") { 59 return { 60 _tag: "Prism", 61 preview: (s: unknown) => flatMapOption(inner.preview)(outer.preview(s)), 62 review: (b: unknown) => outer.review(inner.review(b)), 63 } 64 } 65 66 // Lens + Prism → Optional 67 if (ot === "Lens" && it === "Prism") { 68 return { 69 _tag: "Optional", 70 getOption: (s: unknown) => inner.preview(outer.get(s)), 71 set: (b: unknown) => (s: unknown) => outer.set(inner.review(b))(s), 72 } 73 } 74 75 // Prism + Lens → Optional 76 if (ot === "Prism" && it === "Lens") { 77 return { 78 _tag: "Optional", 79 getOption: (s: unknown) => mapOption(inner.get)(outer.preview(s)), 80 set: (b: unknown) => (s: unknown) => { 81 const oa = outer.preview(s) 82 return isSome(oa) ? outer.review(inner.set(b)(oa.value)) : s 83 }, 84 } 85 } 86 87 // Optional-yielding: covers Lens+Opt, Opt+Lens, Prism+Opt, Opt+Prism, Opt+Opt 88 if (ot !== "Traversal" && it !== "Traversal") { 89 const outerGet = (s: unknown) => 90 ot === "Lens" ? { _tag: "Some" as const, value: outer.get(s) } 91 : (ot === "Prism" ? outer.preview(s) : outer.getOption(s)) 92 return { 93 _tag: "Optional", 94 getOption: (s: unknown) => { 95 const oa = outerGet(s) 96 if (!isSome(oa)) return oa 97 return it === "Lens" ? { _tag: "Some" as const, value: inner.get(oa.value) } 98 : it === "Prism" ? inner.preview(oa.value) 99 : inner.getOption(oa.value) 100 }, 101 set: (b: unknown) => (s: unknown) => { 102 const oa = outerGet(s) 103 if (!isSome(oa)) return s 104 const newA = it === "Prism" ? inner.review(b) : inner.set(b)(oa.value) 105 return ot === "Lens" ? outer.set(newA)(s) 106 : ot === "Prism" ? outer.review(newA) : outer.set(newA)(s) 107 }, 108 } 109 } 110 111 // Traversal-yielding: at least one side is a Traversal 112 const outerGetAll = (s: unknown): readonly unknown[] => 113 ot === "Lens" ? [outer.get(s)] 114 : ot === "Prism" ? (isSome(outer.preview(s)) ? [outer.preview(s).value] : []) 115 : ot === "Optional" ? (isSome(outer.getOption(s)) ? [outer.getOption(s).value] : []) 116 : outer.getAll(s) 117 const innerGetAll = (a: unknown): readonly unknown[] => 118 it === "Lens" ? [inner.get(a)] 119 : it === "Prism" ? (isSome(inner.preview(a)) ? [inner.preview(a).value] : []) 120 : it === "Optional" ? (isSome(inner.getOption(a)) ? [inner.getOption(a).value] : []) 121 : inner.getAll(a) 122 const innerModify = (f: (x: unknown) => unknown) => (a: unknown): unknown => 123 it === "Lens" ? inner.set(f(inner.get(a)))(a) 124 : it === "Prism" ? (isSome(inner.preview(a)) ? inner.review(f(inner.preview(a).value)) : a) 125 : it === "Optional" ? (isSome(inner.getOption(a)) ? inner.set(f(inner.getOption(a).value))(a) : a) 126 : inner.modify(f)(a) 127 const outerModify = (g: (a: unknown) => unknown) => (s: unknown): unknown => 128 ot === "Lens" ? outer.set(g(outer.get(s)))(s) 129 : ot === "Prism" ? (isSome(outer.preview(s)) ? outer.review(g(outer.preview(s).value)) : s) 130 : ot === "Optional" ? (isSome(outer.getOption(s)) ? outer.set(g(outer.getOption(s).value))(s) : s) 131 : outer.modify(g)(s) 132 return { 133 _tag: "Traversal", 134 getAll: (s: unknown) => outerGetAll(s).flatMap((a) => innerGetAll(a)), 135 modify: (f: (x: unknown) => unknown) => (s: unknown) => outerModify(innerModify(f))(s), 136 } 137}