From 1c1ba91e481d91428ccede7ec939161bde0c0328 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 25 Mar 2022 13:09:50 -0700 Subject: [PATCH] feat: Invalidation and lazy reloading of implementations The interface is a bit clunky, the "author" and the callback "data" have to be specified when adding (an) implementation(s) in order for this to work. But it does, and this is just a PoC, the interface could be cleaned up. --- generic/subtract.js | 2 +- picomathInstance.js | 6 ++--- poortf.js | 58 +++++++++++++++++++++++++++++++++++---------- test/_poortf.js | 22 +++++++++++++++-- 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/generic/subtract.js b/generic/subtract.js index c64c15b..87745e0 100644 --- a/generic/subtract.js +++ b/generic/subtract.js @@ -4,6 +4,6 @@ export default function create(pmath) { return pmath( 'subtract', [args => args.length === 2, (x, y) => add(x, negate(y))], - create) + create, pmath) return pmath.subtract } diff --git a/picomathInstance.js b/picomathInstance.js index 0eed730..8bac098 100644 --- a/picomathInstance.js +++ b/picomathInstance.js @@ -6,11 +6,11 @@ export default function picomathInstance (instName) { * as a function, it takes a name and 0 or more implementations add adds * them to its poortf property named name, returning that property value. */ - function fn (name, imps, author) { + function fn (name, imps, author, data) { if (name in fn) { - fn[name].addImps(imps, author) + fn[name].addImps(imps, author, data) } else { - fn[name] = poortf(name, imps, author) + fn[name] = poortf(name, imps, author, data) } return fn[name] } diff --git a/poortf.js b/poortf.js index afbb9dc..68936ab 100644 --- a/poortf.js +++ b/poortf.js @@ -7,23 +7,27 @@ const addImps = (dest, imps, author) => { if (imps) { if (!Array.isArray(imps[0])) imps = [imps] - if (author) { - if (dest.authors.has(author)) { - const [count, index] = dest.authors.get(author) - if (count) dest.imps.splice(index, count) - } - dest.authors.set(author, [imps.length, dest.imps.length]) - } - for (const imp of imps) dest.imps.push(imp) + } else { + imps = [] } + if (author) { + if (dest.authors.has(author)) { + const [count, index] = dest.authors.get(author) + if (count) dest.imps.splice(index, count) + } + dest.authors.set(author, [imps.length, dest.imps.length]) + } + for (const imp of imps) dest.imps.push(imp) } + /* Create a TF, optionally adding some initial imps and further optionally * registering them to an author. There are two advantages to author * registration: (1) if the same author adds implementations, the prior imps * will be deleted, and (2) the author can be invalidated, and the TF will - * lazily call the author back the next time it is called to re-add the imps. + * lazily call the author back with the given data the next time it is + * called, so that it can re-add the imps. */ -export default function poortf (name, imps, author) { +export default function poortf (name, imps, author, data) { /* This is the (function) object we will return */ function fn () { for (const imp of fn.imps) { @@ -37,9 +41,39 @@ export default function poortf (name, imps, author) { Object.defineProperty(fn, 'name', {value: name}) fn.imps = [] fn.authors = new Map() // tracks who made each implementation + fn.authorData = new Map() // tracks author callback data addImps(fn, imps, author) - fn.addImps = (newI, author) => addImps(fn, newI, author) - + if (author) fn.authorData.set(author, data) + fn.addImps = (newI, author, data) => { + addImps(fn, newI, author) + if (author) fn.authorData.set(author, data) + } + fn.invalidate = author => { + addImps(fn, null, author) // deletes the author's imps + fn.unshiftImp([() => true, (...args) => fn.reloadAndRecall(args)]) + } + fn.unshiftImp = newImp => { + fn.imps.unshift(newImp) + for (const record in fn.authors.values()) { + record[1] += 1 + } + } + fn.shiftImp = () => { + const oldImp = fn.imps.shift() + for (const record in fn.authors.values()) { + record[1] -= 1 + } + } + fn.reloadAndRecall = (argArray) => { + fn.shiftImp() // throw ourself away, ha ha + for (const author of fn.authorData.keys()) { + if (fn.authors.get(author)[0] === 0) { + // imps count of 0 means the author was invalidated + author(fn.authorData.get(author)) + } + } + return fn.apply(null, argArray) + } return fn } diff --git a/test/_poortf.js b/test/_poortf.js index 3984407..a5f9f6f 100644 --- a/test/_poortf.js +++ b/test/_poortf.js @@ -21,13 +21,22 @@ describe('poortf', () => { assert.throws(() => add('kilroy'), TypeError) }) + const defNumber = what => { + slate.addImps([args => typeof args[0] === 'number', + n => 'I am not number ' + (what+n)], + defNumber, what+1) + } + it('extends an empty tf', () => { + defNumber(2) + assert.strictEqual(slate(0, 'was here'), 'I am not number 2') + assert.throws(() => slate('Kilroy', 'was here'), TypeError) slate.addImps([ [args => typeof args[0] === 'string', s => s + ' wuz here'], - [args => typeof args[0] === 'number', () => 'I am not a number'] + [args => typeof args[0] === 'boolean', () => 'Maybe I am a number'] ]) assert.strictEqual(slate('Kilroy', 'was here'), 'Kilroy wuz here') - assert.strictEqual(slate(2, 'was here'), 'I am not a number') + assert.strictEqual(slate(true), 'Maybe I am a number') assert.throws(() => slate(['Ha!']), TypeError) }) @@ -36,6 +45,15 @@ describe('poortf', () => { assert.strictEqual(add('Kilroy', 23), 'Kilroy23') assert.throws(() => add(['Ha!'], 'gotcha'), TypeError) }) + + it('can invalidate and lazily reload implementations', () => { + slate.invalidate(defNumber) + assert.strictEqual(slate.imps.length, 3) // reloader plus two non-number + assert.strictEqual(slate(1), 'I am not number 4') // 'what' is now 3!! + assert.strictEqual(slate('Yorlik', 7), 'Yorlik wuz here') + assert.strictEqual(slate(false), 'Maybe I am a number') + assert.strictEqual(slate.imps.length, 3) // reloader gone + }) })