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.
This commit is contained in:
parent
00a7f79552
commit
1c1ba91e48
@ -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
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
|
58
poortf.js
58
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user