UNPKG

monocle-ts-esm

Version:

A porting of scala monocle library to TypeScript

218 lines (217 loc) 8.96 kB
import * as RA from 'fp-ts-esm/ReadonlyArray'; import * as RR from 'fp-ts-esm/ReadonlyRecord'; import { constant, flow, identity } from 'fp-ts-esm/function'; import * as O from 'fp-ts-esm/Option'; import * as E from 'fp-ts-esm/Either'; import { pipe } from 'fp-ts-esm/pipeable'; // ------------------------------------------------------------------------------------- // Iso // ------------------------------------------------------------------------------------- /** @internal */ export const iso = (get, reverseGet) => ({ get, reverseGet }); /** @internal */ export const isoAsLens = (sa) => lens(sa.get, flow(sa.reverseGet, constant)); /** @internal */ export const isoAsPrism = (sa) => prism(flow(sa.get, O.some), sa.reverseGet); /** @internal */ export const isoAsOptional = (sa) => optional(flow(sa.get, O.some), flow(sa.reverseGet, constant)); /** @internal */ export const isoAsTraversal = (sa) => traversal((F) => (f) => (s) => // @ts-expect-error F.map(f(sa.get(s)), (a) => sa.reverseGet(a))); // ------------------------------------------------------------------------------------- // Lens // ------------------------------------------------------------------------------------- /** @internal */ export const lens = (get, set) => ({ get, set }); /** @internal */ export const lensAsOptional = (sa) => optional(flow(sa.get, O.some), sa.set); /** @internal */ export const lensAsTraversal = (sa) => // @ts-expect-error traversal((F) => (f) => (s) => F.map(f(sa.get(s)), (a) => sa.set(a)(s))); /** @internal */ export const lensComposeLens = (ab) => (sa) => lens((s) => ab.get(sa.get(s)), (b) => (s) => sa.set(ab.set(b)(sa.get(s)))(s)); /** @internal */ export const prismComposePrism = (ab) => (sa) => prism(flow(sa.getOption, O.chain(ab.getOption)), flow(ab.reverseGet, sa.reverseGet)); /** @internal */ export const lensComposePrism = (ab) => (sa) => optionalComposeOptional(prismAsOptional(ab))(lensAsOptional(sa)); /** @internal */ export const lensId = () => lens(identity, constant); /** @internal */ export const lensProp = (prop) => (sa) => lens((s) => sa.get(s)[prop], (ap) => (s) => { const oa = sa.get(s); if (ap === oa[prop]) { return s; } return sa.set(Object.assign({}, oa, { [prop]: ap }))(s); }); /** @internal */ export const lensProps = (...props) => (sa) => lens((s) => { const a = sa.get(s); const r = {}; for (const k of props) { r[k] = a[k]; } return r; }, (a) => (s) => { const oa = sa.get(s); for (const k of props) { if (a[k] !== oa[k]) { return sa.set(Object.assign({}, oa, a))(s); } } return s; }); /** @internal */ export const lensComponent = (prop) => (sa) => lens((s) => sa.get(s)[prop], (ap) => (s) => { const oa = sa.get(s); if (ap === oa[prop]) { return s; } const copy = oa.slice(); copy[prop] = ap; return sa.set(copy)(s); }); /** @internal */ export const lensAtKey = (key) => (sa) => pipe(sa, lensComposeLens(atReadonlyRecord().at(key))); // ------------------------------------------------------------------------------------- // Prism // ------------------------------------------------------------------------------------- /** @internal */ export const prism = (getOption, reverseGet) => ({ getOption, reverseGet }); /** @internal */ export const prismAsOptional = (sa) => optional(sa.getOption, (a) => prismSet(a)(sa)); /** @internal */ export const prismAsTraversal = (sa) => traversal((F) => (f) => (s) => pipe(sa.getOption(s), O.fold(() => F.of(s), // @ts-expect-error (a) => F.map(f(a), (a) => prismSet(a)(sa)(s))))); /** @internal */ export const prismModifyOption = (f) => (sa) => (s) => pipe(sa.getOption(s), O.map((o) => { const n = f(o); return n === o ? s : sa.reverseGet(n); })); /** @internal */ export const prismModify = (f) => (sa) => { const g = prismModifyOption(f)(sa); return (s) => pipe(g(s), O.getOrElse(() => s)); }; /** @internal */ export const prismSet = (a) => prismModify(() => a); /** @internal */ export const prismComposeLens = (ab) => (sa) => optionalComposeOptional(lensAsOptional(ab))(prismAsOptional(sa)); /** @internal */ export const prismFromNullable = () => prism(O.fromNullable, identity); /** @internal */ export const prismFromPredicate = (predicate) => prism(O.fromPredicate(predicate), identity); /** @internal */ export const prismSome = () => prism(identity, O.some); /** @internal */ export const prismRight = () => prism(O.fromEither, E.right); /** @internal */ export const prismLeft = () => prism((s) => (E.isLeft(s) ? O.some(s.left) : O.none), // TODO: replace with E.getLeft in v3 E.left); // ------------------------------------------------------------------------------------- // Optional // ------------------------------------------------------------------------------------- /** @internal */ export const optional = (getOption, set) => ({ getOption, set }); /** @internal */ export const optionalAsTraversal = (sa) => traversal((F) => (f) => (s) => pipe(sa.getOption(s), O.fold(() => F.of(s), (a) => F.map(f(a), (a) => sa.set(a)(s))))); /** @internal */ export const optionalModifyOption = (f) => (optional) => (s) => pipe(optional.getOption(s), O.map((a) => { const n = f(a); return n === a ? s : optional.set(n)(s); })); /** @internal */ export const optionalModify = (f) => (optional) => { const g = optionalModifyOption(f)(optional); return (s) => pipe(g(s), O.getOrElse(() => s)); }; /** @internal */ export const optionalComposeOptional = (ab) => (sa) => optional(flow(sa.getOption, O.chain(ab.getOption)), (b) => optionalModify(ab.set(b))(sa)); /** @internal */ export const optionalIndex = (i) => (sa) => pipe(sa, optionalComposeOptional(indexReadonlyArray().index(i))); /** @internal */ export const optionalIndexNonEmpty = (i) => (sa) => pipe(sa, optionalComposeOptional(indexReadonlyNonEmptyArray().index(i))); /** @internal */ export const optionalKey = (key) => (sa) => pipe(sa, optionalComposeOptional(indexReadonlyRecord().index(key))); /** @internal */ export const optionalFindFirst = (predicate) => optional(RA.findFirst(predicate), (a) => (s) => pipe(RA.findIndex(predicate)(s), O.fold(() => s, (i) => RA.unsafeUpdateAt(i, a, s)))); const unsafeUpdateAt = (i, a, as) => { if (as[i] === a) { return as; } else { const xs = [as[0], ...as.slice(1)]; xs[i] = a; return xs; } }; /** @internal */ export const optionalFindFirstNonEmpty = (predicate) => optional(RA.findFirst(predicate), (a) => (as) => pipe(RA.findIndex(predicate)(as), O.fold(() => as, (i) => unsafeUpdateAt(i, a, as)))); // ------------------------------------------------------------------------------------- // Traversal // ------------------------------------------------------------------------------------- /** @internal */ export const traversal = (modifyF) => ({ modifyF }); /** @internal */ export function traversalComposeTraversal(ab) { return (sa) => traversal((F) => (f) => sa.modifyF(F)(ab.modifyF(F)(f))); } /** @internal */ export const ApplicativeIdentity = { URI: 'Identity', map: (fa, f) => f(fa), of: identity, ap: /* istanbul ignore next */ (fab, fa) => fab(fa) }; const isIdentity = (F) => F.URI === 'Identity'; export function fromTraversable(T) { return () => traversal((F) => { // if `F` is `Identity` then `traverseF = map` const traverseF = isIdentity(F) ? T.map : T.traverse(F); return (f) => (s) => traverseF(s, f); }); } /** @internal */ export function traversalTraverse(T) { return traversalComposeTraversal(fromTraversable(T)()); } // ------------------------------------------------------------------------------------- // Ix // ------------------------------------------------------------------------------------- /** @internal */ export const index = (index) => ({ index }); /** @internal */ export const indexReadonlyArray = () => index((i) => optional((as) => RA.lookup(i, as), (a) => (as) => pipe(RA.lookup(i, as), O.fold(() => as, () => RA.unsafeUpdateAt(i, a, as))))); /** @internal */ export const indexReadonlyNonEmptyArray = () => index((i) => optional((as) => RA.lookup(i, as), (a) => (as) => pipe(RA.lookup(i, as), O.fold(() => as, () => unsafeUpdateAt(i, a, as))))); /** @internal */ export const indexReadonlyRecord = () => index((k) => optional((r) => RR.lookup(k, r), (a) => (r) => { if (r[k] === a || O.isNone(RR.lookup(k, r))) { return r; } return RR.insertAt(k, a)(r); })); // ------------------------------------------------------------------------------------- // At // ------------------------------------------------------------------------------------- /** @internal */ export const at = (at) => ({ at }); /** @internal */ export function atReadonlyRecord() { return at((key) => lens((r) => RR.lookup(key, r), O.fold(() => RR.deleteAt(key), (a) => RR.insertAt(key, a)))); }