Skip to content

Commit

Permalink
Use Prism.NONE instead of undefined for prism. See atomicobject#2
Browse files Browse the repository at this point in the history
  • Loading branch information
SpencerPark committed Jul 8, 2018
1 parent 7718bc4 commit 6872fc8
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 70 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 5 additions & 4 deletions src/__tests__/arrays.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import {Arrays} from '../arrays';
import * as jsv from 'jsverify';
import {isEqual} from 'lodash';
import {Prism} from '../index';


describe('Arrays', () => {
describe('index', () => {
it('is a prism for the specified index', () => {
const a = [1,2,3];

expect(Arrays.index<number>(-1)(a)).toBe(undefined);
expect(Arrays.index<number>(-1)(a)).toBe(Prism.NONE);
expect(Arrays.index<number>(0)(a)).toBe(1);
expect(Arrays.index<number>(1)(a)).toBe(2);
expect(Arrays.index<number>(2)(a)).toBe(3);
expect(Arrays.index<number>(3)(a)).toBe(undefined);
expect(Arrays.index<number>(3)(a)).toBe(Prism.NONE);

const b = Arrays.index<number>(2).set(a,5);
const c = Arrays.index<number>(3).set(a,9999);
Expand All @@ -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);
Expand Down Expand Up @@ -96,4 +97,4 @@ describe('Arrays', () => {
expect(shift([])).toEqual([])
})
});
})
});
14 changes: 7 additions & 7 deletions src/__tests__/lenses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ describe("Prism", () => {
return Prism.of<T[], T>({
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);
Expand Down Expand Up @@ -237,7 +237,7 @@ describe("Prism", () => {
it("short circuits on undefined", () => {
let a = ["a"];
let index1 = arrayIndex<string>(1);
expect(index1.get(a)).toEqual(undefined);
expect(index1.get(a)).toBe(Prism.NONE);

let a2 = index1.set(a, "b");
expect(a2).toEqual(["a"]);
Expand Down Expand Up @@ -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: [] });
});

Expand All @@ -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([]);
});
});
Expand Down
6 changes: 3 additions & 3 deletions src/arrays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export namespace Arrays {

return Prism.of<T[], T>({
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);
Expand All @@ -34,7 +34,7 @@ export namespace Arrays {
...add,
...a.slice(i + del, a.length)
]
};
}

/** Like Array#pop but returns a copy */
export function pop<T>(a: T[]): T[] {
Expand Down
109 changes: 58 additions & 51 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ export interface Lens<T,V> {

/** Core prism shape. Used to construct Prisms and can be passed to higher-order Prism functions, such as comp */
export interface IPrism<T, V> {
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,V> {
(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<V>): (t:T) => T;
update(t:T, fn: Prism.Updater<V>): T;

comp<V2>(l: Prism.Prismish<V, V2>): Prism<T, V2>;
}
Expand All @@ -60,60 +60,67 @@ export type Isomorphism<T, V> = {

/** Core module for creating and using prisms, which are get/set proxies that gracefully handle undefined. */
export namespace Prism {
export function of<T,V>(spec: IPrism<T,V>): Prism<T,V> {
var func = <Prism<T,V>>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, <V>tOrV);
} else {
return spec.set(<T>tOrV, v!);
}
class _None {}
export type NONE = _None;
export const NONE: NONE = new _None();

export const isNone = <V>(v: V | NONE): v is NONE => v === NONE;
export const isNotNone = <V>(v: V | NONE): v is V => v !== NONE;

export type Updater<V> = (v: V | Prism.NONE) => V | Prism.NONE;

export function of<T, V>(spec: IPrism<T, V>): Prism<T, V> {
const func = (o => spec.get(o)) as Prism<T, V>;

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, <any>tOrFn);
} else {
const t = <T>tOrFn;
const v = spec.get(t);
if (v === undefined) {
return t;
} else {
return spec.set(t, <V>f(v));
}
}
function update(fn: Prism.Updater<V>): (t:T) => T;
function update(t:T, fn: Prism.Updater<V>): T;
function update(tOrFn: T | Prism.Updater<V>, fn?: Prism.Updater<V>) {
if (arguments.length === 1)
return (t:T) => func.update(t, tOrFn as Prism.Updater<V>);

const t = tOrFn as T;
let v = spec.get(t);
if (isNone(v))
return t;

v = (fn as Prism.Updater<V>)(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 <Prism<T,V>>func;
func.get = spec.get;
func.set = set;
func.update = update;
func.comp = comp;
return func;
}

export type Prismish<T,U> = ILens<T,U> | IPrism<T,U>
export type Prismish<T, U> = IPrism<T, U> | ILens<T, U>;

export function comp<T, U, V>(l1: Prismish<T, U>, l2: Prismish<U, V>): Prism<T, V>;
export function comp<T, U1, U2, V>(l1: Prismish<T, U1>, l2: Prismish<U1, U2>, l3: IPrism<U2, V>): Prism<T, V>;
export function comp<T, U1, U2, U3, V>(l1: Prismish<T, U1>, l2: Prismish<U1, U2>, l3: IPrism<U2, U3>, l4: Prismish<U3, V>): Prism<T, V>;
export function comp<T, U1, U2, U3, U4, V>(l1: Prismish<T, U1>, l2: Prismish<U1, U2>, l3: IPrism<U2, U3>, l4: Prismish<U3, U4>, l5: Prismish<U4, V>): Prism<T, V>;
export function comp(...prisms: Prismish<any, any>[]): Prism<any, any> {
export function comp<T, V>(...prisms: Prismish<any, any>[]): Prism<T, V> {
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<T, V>({
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);
}
});
}
Expand Down Expand Up @@ -173,18 +180,18 @@ export interface SafeUpdate {

/** Factory to create monomorphic composed setters */
function makeComposedSetter() {
const performComposedSet = (o: any, v: any, lenses: ILens<any, any>[], index: number) => {
if (index == lenses.length - 1) {
return lenses[index].set(o, v);
const performComposedSet = (o: any, v: any, prisms: Prism.Prismish<any, any>[], 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;
}

Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"noImplicitAny": false,
"sourceMap": true,
"strict": true,
"declaration": true,
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 6872fc8

Please sign in to comment.