Frptools uses a hash function now instead of a comparator

This commit is contained in:
Timothy Farrell 2017-11-20 20:57:18 -06:00
parent 46fdb975c5
commit 0b26e63061
7 changed files with 58 additions and 54 deletions

View File

@ -51,22 +51,24 @@ const remainingSubscriptionCount = unsubscribe();
inViewport.unsubscribeAll(); // Call unsubscribeAll to remove child property/computed subscriptions. inViewport.unsubscribeAll(); // Call unsubscribeAll to remove child property/computed subscriptions.
``` ```
### Provide a comparator for complex types ### Provide a hash function for complex types
When storing a type that is not determined to be equal with simple equality (===), provide a When storing a type that is not determined to be equal with simple equality (===), provide a hash
function to determine in the new provided value should be propagated to dependents. function to be used for simple comparison to determine if the new provided value should be
propagated to dependents.
```js ```js
function setEquals(a, b) { function hashSet(_a) {
return ( if (_a instanceof Set) {
a instanceof Set && return Array.from(_a.keys())
b instanceof Set && .sort()
[...a].reduce((acc, d) => acc && b.has(d), true) && .map(k => `${(typeof k).substr(0, 1)}:${encodeURIComponent(k)}/`)
[...b].reduce((acc, d) => acc && a.has(d), true) .join('?');
); }
return _a;
} }
const a = prop(new Set([1, 2]), setEquals); const a = prop(new Set([1, 2]), hashSet);
``` ```
# [computed](./src/computed.js) # [computed](./src/computed.js)
@ -137,28 +139,30 @@ showDialog.detach(); // Call detach to remove this computed from the logic tree.
showDialog.unsubscribeAll(); // Call unsubscribeAll to remove child property/computed subscriptions. showDialog.unsubscribeAll(); // Call unsubscribeAll to remove child property/computed subscriptions.
``` ```
### Provide a comparator for complex types ### Provide a hash function for complex types
When the computed result is a type that is not determined to be equal with simple equality (===), When the computed result is a type that is not determined to be equal with simple equality (===),
provide a function to determine in the new provided value should be propagated to dependents. provide a hash function to be used for simple comparison to determine if the new provided value
should be propagated to dependents.
```js ```js
function setEquals(a, b) { function hashSet(_a) {
return ( if (_a instanceof Set) {
a instanceof Set && return Array.from(_a.keys())
b instanceof Set && .sort()
[...a].reduce((acc, d) => acc && b.has(d), true) && .map(k => `${(typeof k).substr(0, 1)}:${encodeURIComponent(k)}/`)
[...b].reduce((acc, d) => acc && a.has(d), true) .join('?');
); }
return _a;
} }
function _intersection(a, b) { function _intersection(a, b) {
return new Set([...a].filter(x => b.has(x))); return new Set([...a].filter(x => b.has(x)));
} }
const a = prop(new Set([1, 2]), setEquals); const a = prop(new Set([1, 2]), hashSet);
const b = prop(new Set([2, 3]), setEquals); const b = prop(new Set([2, 3]), hashSet);
const intersection = computed(_intersection, [a, b], setEquals); const intersection = computed(_intersection, [a, b], hashSet);
``` ```
# [bundle](./src/bundle.js) # [bundle](./src/bundle.js)

View File

@ -1,6 +1,6 @@
{ {
"name": "frptools", "name": "frptools",
"version": "2.0.0", "version": "2.1.0",
"description": "Observable Property and Computed data streams", "description": "Observable Property and Computed data streams",
"main": "lib/index.js", "main": "lib/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",

View File

@ -1,4 +1,5 @@
const { prop, computed } = require('../lib/index.js'); const { prop, computed } = require('../lib/index.js');
const { hashSet } = require('../lib/util.js');
describe('computed', () => { describe('computed', () => {
const add = (a, b) => a + b; const add = (a, b) => a + b;
@ -157,15 +158,6 @@ describe('computed', () => {
}); });
it('uses a comparator', () => { it('uses a comparator', () => {
function setEquals(a, b) {
return (
a instanceof Set &&
b instanceof Set &&
[...a].reduce((acc, d) => acc && b.has(d), true) &&
[...b].reduce((acc, d) => acc && a.has(d), true)
);
}
let runCount = 0; let runCount = 0;
function intersection(a, b) { function intersection(a, b) {
@ -173,9 +165,9 @@ describe('computed', () => {
return new Set([...a].filter(x => b.has(x))); return new Set([...a].filter(x => b.has(x)));
} }
const a = prop(new Set([1, 2]), setEquals); const a = prop(new Set([1, 2]), hashSet);
const b = prop(new Set([2, 3]), setEquals); const b = prop(new Set([2, 3]), hashSet);
const ABintersection = computed(intersection, [a, b], setEquals); const ABintersection = computed(intersection, [a, b], hashSet);
expect(runCount).toEqual(0); expect(runCount).toEqual(0);
expect([...ABintersection()]).toEqual([2]); expect([...ABintersection()]).toEqual([2]);

View File

@ -1,4 +1,5 @@
const { prop } = require('../lib/index.js'); const { prop } = require('../lib/index.js');
const { hashSet } = require('../lib/util.js');
describe('A property', () => { describe('A property', () => {
it('returns its initialized value', () => { it('returns its initialized value', () => {
@ -62,19 +63,10 @@ describe('A property', () => {
expect(runCount).toEqual(3); expect(runCount).toEqual(3);
}); });
it('uses a comparator', () => { it('uses a hash function', () => {
function setEquals(a, b) {
return (
a instanceof Set &&
b instanceof Set &&
[...a].reduce((acc, d) => acc && b.has(d), true) &&
[...b].reduce((acc, d) => acc && a.has(d), true)
);
}
let runCount = 0; let runCount = 0;
const a = prop(new Set([1, 2]), setEquals); const a = prop(new Set([1, 2]), hashSet);
a.subscribe(() => (runCount += 1)); a.subscribe(() => (runCount += 1));
expect([...a()]).toEqual([1, 2]); expect([...a()]).toEqual([1, 2]);
expect(runCount).toEqual(0); expect(runCount).toEqual(0);

View File

@ -1,10 +1,11 @@
import { eq } from './util.js'; import { id } from './util.js';
export function computed(fn, dependencies = [], comparator = eq) { export function computed(fn, dependencies = [], hash = id) {
const subscribers = new Set(); const subscribers = new Set();
const dependents = new Set(); const dependents = new Set();
let isDirty = true; let isDirty = true;
let val; let val;
let id;
// Receive dirty flag from parent logic node (dependency). Pass it down. // Receive dirty flag from parent logic node (dependency). Pass it down.
function _computedDirtyReporter(_, skipPropagation) { function _computedDirtyReporter(_, skipPropagation) {
@ -25,7 +26,9 @@ export function computed(fn, dependencies = [], comparator = eq) {
if (isDirty) { if (isDirty) {
const newVal = fn.apply(null, dependencies.map(runParam)); const newVal = fn.apply(null, dependencies.map(runParam));
isDirty = false; isDirty = false;
if (!comparator(val, newVal)) { const newId = hash(newVal);
if (id !== newId) {
id = newId;
val = newVal; val = newVal;
subscribers.forEach(s => s(val)); subscribers.forEach(s => s(val));
} }

View File

@ -1,10 +1,13 @@
import { eq } from './util.js'; import { id } from './util.js';
export function prop(store, comparator = eq) { export function prop(store, hash = id) {
const subscribers = new Set(); const subscribers = new Set();
let id = hash(store);
const accessor = function _prop(newVal) { const accessor = function _prop(newVal) {
if (newVal !== undefined && !comparator(store, newVal)) { const newId = hash(newVal);
if (newVal !== undefined && id !== newId) {
id = newId;
store = newVal; store = newVal;
subscribers.forEach(s => s(store)); subscribers.forEach(s => s(store));
} }

View File

@ -1 +1,11 @@
export const eq = (a, b) => a === b; export const id = a => a;
export function hashSet(_a) {
if (_a instanceof Set) {
return Array.from(_a.keys())
.sort()
.map(k => `${(typeof k).substr(0, 1)}:${encodeURIComponent(k)}/`)
.join('?');
}
return _a;
}