Glen Whitney
1c1ba91e48
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.
83 lines
2.7 KiB
JavaScript
83 lines
2.7 KiB
JavaScript
/* Totally minimal "typed functions" */
|
|
|
|
/* helper: dest is really a TF and imps is either nothing, an imp, or
|
|
* an array of implementations. author is the entity responsible for these
|
|
* implementations.
|
|
*/
|
|
const addImps = (dest, imps, author) => {
|
|
if (imps) {
|
|
if (!Array.isArray(imps[0])) imps = [imps]
|
|
} 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 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, data) {
|
|
/* This is the (function) object we will return */
|
|
function fn () {
|
|
for (const imp of fn.imps) {
|
|
if (imp[0](arguments)) return imp[1].apply(null, arguments)
|
|
}
|
|
throw new TypeError(
|
|
`TF ${fn.name}: No match for ${arguments[0]}, ${arguments[1]}, ...`)
|
|
}
|
|
|
|
/* Now dress it up for use */
|
|
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)
|
|
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
|
|
}
|
|
|
|
export function isTF(x) {
|
|
return typeof x === 'function' && 'imps' in x && Array.isArray(x.imps)
|
|
}
|