From 6872fc886f9f8f66d3c3daa877d2c2dd4c62a87a Mon Sep 17 00:00:00 2001 From: SpencerPark Date: Sat, 7 Jul 2018 23:39:04 -0400 Subject: [PATCH] Use Prism.NONE instead of undefined for prism. See #2 --- package.json | 2 +- src/__tests__/arrays.test.ts | 9 +-- src/__tests__/lenses.test.ts | 14 ++--- src/arrays.ts | 6 +- src/index.ts | 109 +++++++++++++++++++---------------- tsconfig.json | 1 - yarn.lock | 6 +- 7 files changed, 77 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 0a21bab..9998882 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "jsverify": "^0.8.2", "lodash": "^4.17.4", "ts-jest": "^20.0.11", - "typescript": "^2.4.2" + "typescript": "2.9.2" }, "directories": { "lib": "./lib" diff --git a/src/__tests__/arrays.test.ts b/src/__tests__/arrays.test.ts index 0a68dbb..80233b3 100644 --- a/src/__tests__/arrays.test.ts +++ b/src/__tests__/arrays.test.ts @@ -1,6 +1,7 @@ import {Arrays} from '../arrays'; import * as jsv from 'jsverify'; import {isEqual} from 'lodash'; +import {Prism} from '../index'; describe('Arrays', () => { @@ -8,11 +9,11 @@ describe('Arrays', () => { it('is a prism for the specified index', () => { const a = [1,2,3]; - expect(Arrays.index(-1)(a)).toBe(undefined); + expect(Arrays.index(-1)(a)).toBe(Prism.NONE); expect(Arrays.index(0)(a)).toBe(1); expect(Arrays.index(1)(a)).toBe(2); expect(Arrays.index(2)(a)).toBe(3); - expect(Arrays.index(3)(a)).toBe(undefined); + expect(Arrays.index(3)(a)).toBe(Prism.NONE); const b = Arrays.index(2).set(a,5); const c = Arrays.index(3).set(a,9999); @@ -38,7 +39,7 @@ describe('Arrays', () => { }); it('really is like Array#splice', () => { - var prop = jsv.forall('array nat', 'nat', 'nat', 'array nat', + const prop = jsv.forall('array nat', 'nat', 'nat', 'array nat', (a: number[], i: number, del: number, add: number[]) => { const nativeResult = a.slice(); nativeResult.splice(i, del, ...add); @@ -96,4 +97,4 @@ describe('Arrays', () => { expect(shift([])).toEqual([]) }) }); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/src/__tests__/lenses.test.ts b/src/__tests__/lenses.test.ts index 358c79e..c96cacd 100644 --- a/src/__tests__/lenses.test.ts +++ b/src/__tests__/lenses.test.ts @@ -209,7 +209,7 @@ describe("Prism", () => { return Prism.of({ get(a) { const i = index(a, n); - return i !== undefined ? a[i] : undefined; + return i !== undefined ? a[i] : Prism.NONE; }, set(a, v) { const i = index(a, n); @@ -237,7 +237,7 @@ describe("Prism", () => { it("short circuits on undefined", () => { let a = ["a"]; let index1 = arrayIndex(1); - expect(index1.get(a)).toEqual(undefined); + expect(index1.get(a)).toBe(Prism.NONE); let a2 = index1.set(a, "b"); expect(a2).toEqual(["a"]); @@ -270,8 +270,8 @@ describe("Prism", () => { }); const empty = { array: [] }; - expect(composed(empty)).toBeUndefined(); - expect(composed.get(empty)).toBeUndefined(); + expect(composed(empty)).toBe(Prism.NONE); + expect(composed.get(empty)).toBe(Prism.NONE); expect(composed.set(empty, 1)).toEqual({ array: [] }); }); @@ -290,9 +290,9 @@ describe("Prism", () => { expect(composed.get(arr)).toBe(1); expect(composed.set(arr, 10)).toEqual([{ prop: 10 }]); - const empty = []; - expect(composed(empty)).toBeUndefined(); - expect(composed.get(empty)).toBeUndefined(); + const empty: Inner[] = []; + expect(composed(empty)).toBe(Prism.NONE); + expect(composed.get(empty)).toBe(Prism.NONE); expect(composed.set(empty, 1)).toEqual([]); }); }); diff --git a/src/arrays.ts b/src/arrays.ts index 0230125..d474fc5 100644 --- a/src/arrays.ts +++ b/src/arrays.ts @@ -12,8 +12,8 @@ export namespace Arrays { return Prism.of({ get(a) { - const i = index(a, n) - return i !== undefined ? a[i] : undefined; + const i = index(a, n); + return i !== undefined ? a[i] : Prism.NONE; }, set(a, v) { const i = index(a, n); @@ -34,7 +34,7 @@ export namespace Arrays { ...add, ...a.slice(i + del, a.length) ] - }; + } /** Like Array#pop but returns a copy */ export function pop(a: T[]): T[] { diff --git a/src/index.ts b/src/index.ts index 09814b4..b474139 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,20 +34,20 @@ export interface Lens { /** Core prism shape. Used to construct Prisms and can be passed to higher-order Prism functions, such as comp */ export interface IPrism { - get(t: T): V | undefined; - set(a: T, value: V): T + get(t: T): V | Prism.NONE; + set(t: T, value: V): T } /** An object which can be used for getting and copy-and-updating potentially-undefined substructure of objects. Like lens, but used for optional things. */ export interface Prism { - (t:T): V | undefined; - get(t:T): V | undefined; + (t:T): V | Prism.NONE; + get(t:T): V | Prism.NONE; set(v:V): (t:T) => T; set(t:T, v:V): T; - update(fn: (v:V | undefined) => V | undefined): (t:T) => T; - update(t:T, fn: (v:V | undefined) => V | undefined): T; + update(fn: Prism.Updater): (t:T) => T; + update(t:T, fn: Prism.Updater): T; comp(l: Prism.Prismish): Prism; } @@ -60,60 +60,67 @@ export type Isomorphism = { /** Core module for creating and using prisms, which are get/set proxies that gracefully handle undefined. */ export namespace Prism { - export function of(spec: IPrism): Prism { - var func = >function (o: T) { - return spec.get(o); - }; - - const set = function(tOrV: T|V, v?: V) { - if (arguments.length === 1) { - return (t:T) => spec.set(t, tOrV); - } else { - return spec.set(tOrV, v!); - } + class _None {} + export type NONE = _None; + export const NONE: NONE = new _None(); + + export const isNone = (v: V | NONE): v is NONE => v === NONE; + export const isNotNone = (v: V | NONE): v is V => v !== NONE; + + export type Updater = (v: V | Prism.NONE) => V | Prism.NONE; + + export function of(spec: IPrism): Prism { + const func = (o => spec.get(o)) as Prism; + + function set(v: V): (t: T) => T; + function set(t: T, v: V): T; + function set(tOrV: T | V, v?: V) { + if (arguments.length === 1) + return (t:T) => spec.set(t, tOrV as V); + + return spec.set(tOrV as T, v as V); } - const update = (tOrFn: T|Function, f?: Function) => { - if (f === undefined) { - return (t:T) => func.update(t, tOrFn); - } else { - const t = tOrFn; - const v = spec.get(t); - if (v === undefined) { - return t; - } else { - return spec.set(t, f(v)); - } - } + function update(fn: Prism.Updater): (t:T) => T; + function update(t:T, fn: Prism.Updater): T; + function update(tOrFn: T | Prism.Updater, fn?: Prism.Updater) { + if (arguments.length === 1) + return (t:T) => func.update(t, tOrFn as Prism.Updater); + + const t = tOrFn as T; + let v = spec.get(t); + if (isNone(v)) + return t; + + v = (fn as Prism.Updater)(v); + return isNotNone(v) ? spec.set(t, v) : t; } - const comp = (...prisms: any[]) => - (Prism.comp as any)(func, ...prisms); + const comp = Prism.comp.bind(undefined, func); - (func as any).get = spec.get; - (func as any).set = set; - (func as any).update = update; - (func as any).comp = comp; - return >func; + func.get = spec.get; + func.set = set; + func.update = update; + func.comp = comp; + return func; } - export type Prismish = ILens | IPrism + export type Prismish = IPrism | ILens; export function comp(l1: Prismish, l2: Prismish): Prism; export function comp(l1: Prismish, l2: Prismish, l3: IPrism): Prism; export function comp(l1: Prismish, l2: Prismish, l3: IPrism, l4: Prismish): Prism; export function comp(l1: Prismish, l2: Prismish, l3: IPrism, l4: Prismish, l5: Prismish): Prism; - export function comp(...prisms: Prismish[]): Prism { + export function comp(...prisms: Prismish[]): Prism { let performComposedSet: any; - return Prism.of({ - get: (o: any) => - prisms.reduce((o, l) => o === undefined ? o : l.get(o), o), - - set: (o: any, v: any) => { + return Prism.of({ + get: t => + prisms.reduce((prevT, p) => isNone(prevT) ? Prism.NONE : p.get(prevT), t), + set: (t, v) => { if (performComposedSet === undefined) { performComposedSet = makeComposedSetter(); } - return performComposedSet(o, v, prisms, 0); + return performComposedSet(t, v, prisms, 0); } }); } @@ -173,18 +180,18 @@ export interface SafeUpdate { /** Factory to create monomorphic composed setters */ function makeComposedSetter() { - const performComposedSet = (o: any, v: any, lenses: ILens[], index: number) => { - if (index == lenses.length - 1) { - return lenses[index].set(o, v); + const performComposedSet = (o: any, v: any, prisms: Prism.Prismish[], index: number): any => { + if (index == prisms.length - 1) { + return prisms[index].set(o, v); } else { - const inner = lenses[index].get(o); - if (inner) { - return lenses[index].set(o, performComposedSet(inner, v, lenses, index + 1)); + const inner = prisms[index].get(o); + if (Prism.isNotNone(inner)) { + return prisms[index].set(o, performComposedSet(inner, v, prisms, index + 1)); } else { return o; } } - } + }; return performComposedSet; } diff --git a/tsconfig.json b/tsconfig.json index 369cd8c..dd21e1b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "module": "commonjs", "moduleResolution": "node", "target": "es5", - "noImplicitAny": false, "sourceMap": true, "strict": true, "declaration": true, diff --git a/yarn.lock b/yarn.lock index 2bb10d0..131501f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2087,9 +2087,9 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -typescript@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" +typescript@2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" typify-parser@^1.1.0: version "1.1.0"