Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
d95e5ad930 | |||
4213ade4ba | |||
1c1ba91e48 | |||
00a7f79552 |
@ -2,6 +2,7 @@ import { anyComplex } from './complex.js'
|
|||||||
|
|
||||||
export default function create(pmath) {
|
export default function create(pmath) {
|
||||||
const complex = pmath('complex')
|
const complex = pmath('complex')
|
||||||
|
const resolve = pmath.config.resolveComplex
|
||||||
return pmath('add', [anyComplex, // naive, but this is just a P-o-C
|
return pmath('add', [anyComplex, // naive, but this is just a P-o-C
|
||||||
(...addends) => {
|
(...addends) => {
|
||||||
let sum = complex(addends[0])
|
let sum = complex(addends[0])
|
||||||
@ -10,7 +11,9 @@ export default function create(pmath) {
|
|||||||
sum.re += addend.re
|
sum.re += addend.re
|
||||||
sum.im += addend.im
|
sum.im += addend.im
|
||||||
}
|
}
|
||||||
|
if (resolve && Math.abs(sum.im/sum.re) < 1e-10) return sum.re
|
||||||
return sum
|
return sum
|
||||||
}])
|
}],
|
||||||
|
create, pmath) // register ourselves for invalidation and reconfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
export default function create(pmath) {
|
export default function create(pmath) {
|
||||||
const add = pmath('add')
|
const add = pmath('add')
|
||||||
const negate = pmath('negate')
|
const negate = pmath('negate')
|
||||||
if (!pmath.subtract) { // avoid double definition at cost of extensibility
|
return pmath(
|
||||||
pmath('subtract', [args => args.length === 2,
|
'subtract',
|
||||||
(x, y) => add(x, negate(y))])
|
[args => args.length === 2, (x, y) => add(x, negate(y))],
|
||||||
}
|
create, pmath)
|
||||||
return pmath.subtract
|
return pmath.subtract
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,43 @@
|
|||||||
/* Core of picomath: generates an instance */
|
/* Core of picomath: generates an instance */
|
||||||
import poortf from './poortf.js'
|
import poortf from './poortf.js'
|
||||||
|
|
||||||
export default function picomathInstance (instName) {
|
export default function picomathInstance (instName, config) {
|
||||||
/* Since we have to do it all the time, when we call a picomath instance
|
/* Since we have to do it all the time, when we call a picomath instance
|
||||||
* as a function, it takes a name and 0 or more implementations add adds
|
* 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.
|
* them to its poortf property named name, returning that property value.
|
||||||
*/
|
*/
|
||||||
function fn (name, imps) {
|
function fn (name, imps, author, data) {
|
||||||
if (name in fn) {
|
if (name in fn) {
|
||||||
fn[name].addImps(imps)
|
fn[name].addImps(imps, author, data)
|
||||||
} else {
|
} else {
|
||||||
fn[name] = poortf(name, imps)
|
fn[name] = poortf(name, imps, author, data)
|
||||||
|
}
|
||||||
|
/* For PoC, just assume only reason to provide author info
|
||||||
|
* is to be invalidated when config changes
|
||||||
|
*/
|
||||||
|
if (author) {
|
||||||
|
if (!fn.configUsers.has(author)) {
|
||||||
|
fn.configUsers.set(author, new Set())
|
||||||
|
}
|
||||||
|
fn.configUsers.get(author).add(name)
|
||||||
}
|
}
|
||||||
return fn[name]
|
return fn[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(fn, 'name', {value: instName})
|
Object.defineProperty(fn, 'name', {value: instName})
|
||||||
|
// There is an issue below of possible collision between the following
|
||||||
|
// property names and operation names. Since it would not be too hard to
|
||||||
|
// solve, we won't worry about it in this PoC.
|
||||||
|
fn.config = config || { resolveComplex: false } // primitive default for POC
|
||||||
|
fn.configUsers = new Map()
|
||||||
|
fn.reconfigure = config => {
|
||||||
|
for (const [author, names] of fn.configUsers.entries()) {
|
||||||
|
for (const name of names) {
|
||||||
|
fn[name].invalidate(author)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn.config = config
|
||||||
|
}
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
63
poortf.js
63
poortf.js
@ -1,13 +1,33 @@
|
|||||||
/* Totally minimal "typed functions" */
|
/* Totally minimal "typed functions" */
|
||||||
|
|
||||||
const addImps = (dest, imps) => {
|
/* 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 (imps) {
|
||||||
if (!Array.isArray(imps[0])) imps = [imps]
|
if (!Array.isArray(imps[0])) imps = [imps]
|
||||||
for (const imp of imps) dest.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function poortf (name, imps) {
|
/* 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 */
|
/* This is the (function) object we will return */
|
||||||
function fn () {
|
function fn () {
|
||||||
for (const imp of fn.imps) {
|
for (const imp of fn.imps) {
|
||||||
@ -20,9 +40,40 @@ export default function poortf (name, imps) {
|
|||||||
/* Now dress it up for use */
|
/* Now dress it up for use */
|
||||||
Object.defineProperty(fn, 'name', {value: name})
|
Object.defineProperty(fn, 'name', {value: name})
|
||||||
fn.imps = []
|
fn.imps = []
|
||||||
addImps(fn.imps, imps)
|
fn.authors = new Map() // tracks who made each implementation
|
||||||
fn.addImps = newI => addImps(fn.imps, newI)
|
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
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,4 +20,8 @@ describe('The default full picomath instance "math"', () => {
|
|||||||
math.complex(11, -4))
|
math.complex(11, -4))
|
||||||
assert.deepStrictEqual(math.negate(math.complex(3, '8')).im, -8)
|
assert.deepStrictEqual(math.negate(math.complex(3, '8')).im, -8)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not double-define subtract', () => {
|
||||||
|
assert.deepStrictEqual(math.subtract.imps.length, 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -21,13 +21,22 @@ describe('poortf', () => {
|
|||||||
assert.throws(() => add('kilroy'), TypeError)
|
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', () => {
|
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([
|
slate.addImps([
|
||||||
[args => typeof args[0] === 'string', s => s + ' wuz here'],
|
[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('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)
|
assert.throws(() => slate(['Ha!']), TypeError)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -36,6 +45,15 @@ describe('poortf', () => {
|
|||||||
assert.strictEqual(add('Kilroy', 23), 'Kilroy23')
|
assert.strictEqual(add('Kilroy', 23), 'Kilroy23')
|
||||||
assert.throws(() => add(['Ha!'], 'gotcha'), TypeError)
|
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
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,4 +36,20 @@ describe('Custom instances', () => {
|
|||||||
assert.strictEqual('subtract' in cherry, false)
|
assert.strictEqual('subtract' in cherry, false)
|
||||||
assert.strictEqual('negate' in cherry, false)
|
assert.strictEqual('negate' in cherry, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const res = picoInstance('resolving', { resolveComplex: true })
|
||||||
|
createNumbers(res)
|
||||||
|
createComplex(res)
|
||||||
|
|
||||||
|
it("can be configured", () => {
|
||||||
|
assert.strictEqual(res.add(res.complex(2,3), res.complex(2,-3)), 4)
|
||||||
|
assert.deepStrictEqual(math.add(math.complex(2,3), math.complex(2,-3)),
|
||||||
|
math.complex(4,0))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can be reconfigured", () => {
|
||||||
|
res.reconfigure({resolveComplex: false})
|
||||||
|
assert.deepStrictEqual(math.add(math.complex(2,3), math.complex(2,-3)),
|
||||||
|
math.complex(4,0))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user