fix: Separate typed instance for each PocomathInstance (#15)
Also starts each PocomathInstance with no types at all, and uses the new situation to eliminate the need for a Complex "base case". Resolves #14. Resolves #13. Co-authored-by: Glen Whitney <glen@studioinfinity.org> Reviewed-on: #15
This commit is contained in:
parent
ed71b15969
commit
0069597a76
@ -1,3 +0,0 @@
|
|||||||
import typed from 'typed-function'
|
|
||||||
|
|
||||||
typed.addType({name: 'bigint', test: b => typeof b === 'bigint'})
|
|
@ -1,15 +0,0 @@
|
|||||||
import {numComplex} from './types/Complex.mjs'
|
|
||||||
|
|
||||||
export const add = {
|
|
||||||
'...Complex': [['self'], ref => addends => {
|
|
||||||
if (addends.length === 0) return {re:0, im:0}
|
|
||||||
const seed = addends.shift()
|
|
||||||
return addends.reduce((w,z) => {
|
|
||||||
/* Need a "base case" to avoid infinite self-reference loops */
|
|
||||||
if (numComplex(z) && numComplex(w)) {
|
|
||||||
return {re: w.re + z.re, im: w.im + z.im}
|
|
||||||
}
|
|
||||||
return {re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}
|
|
||||||
}, seed)
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import {numComplex} from './types/Complex.mjs'
|
|
||||||
export const negate = {
|
|
||||||
Complex: [['self'], ref => z => {
|
|
||||||
/* need a "base case" to avoid infinite self-reference */
|
|
||||||
if (numComplex(z)) return {re: -z.re, im: -z.im}
|
|
||||||
return {re: ref.self(z.re), im: ref.self(z.im)}
|
|
||||||
}]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export const negate = {
|
|
||||||
number: [[], n => -n]
|
|
||||||
}
|
|
@ -5,7 +5,9 @@
|
|||||||
inclusion, avoiding factory functions.',
|
inclusion, avoiding factory functions.',
|
||||||
main: 'index.js',
|
main: 'index.js',
|
||||||
scripts: {
|
scripts: {
|
||||||
test: '!(find . | sort -f | uniq -i -c | grep -v " 1 ") && npx mocha',
|
'test:filecase': '!(find . | sort -f | uniq -i -c | grep -v " 1 ")',
|
||||||
|
'test:unit': 'npx mocha --recursive',
|
||||||
|
test: 'pnpm test:filecase && pnpm test:unit',
|
||||||
},
|
},
|
||||||
repository: {
|
repository: {
|
||||||
type: 'git',
|
type: 'git',
|
||||||
|
3
src/bigint/Types/bigint.mjs
Normal file
3
src/bigint/Types/bigint.mjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const Types = {
|
||||||
|
bigint: {test: b => typeof b === 'bigint'}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import './BigInt.mjs'
|
export {Types} from './Types/bigint.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {
|
||||||
'...bigint': [[], addends => addends.reduce((x,y) => x+y, 0n)],
|
'...bigint': [[], addends => addends.reduce((x,y) => x+y, 0n)],
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export {Types} from './Types/bigint.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {negate} from './negate.mjs'
|
export {negate} from './negate.mjs'
|
||||||
export {subtract} from '../generic/subtract.mjs'
|
export {subtract} from '../generic/subtract.mjs'
|
@ -1,2 +1,3 @@
|
|||||||
import './BigInt.mjs'
|
export {Types} from './Types/bigint.mjs'
|
||||||
|
|
||||||
export const negate = {bigint: [[], b => -b ]}
|
export const negate = {bigint: [[], b => -b ]}
|
@ -1,5 +1,3 @@
|
|||||||
import typed from 'typed-function'
|
|
||||||
|
|
||||||
/* Use a plain object with keys re and im for a complex; note the components
|
/* Use a plain object with keys re and im for a complex; note the components
|
||||||
* can be any type (for this proof-of-concept; in reality we'd want to
|
* can be any type (for this proof-of-concept; in reality we'd want to
|
||||||
* insist on some numeric or scalar supertype).
|
* insist on some numeric or scalar supertype).
|
||||||
@ -8,20 +6,13 @@ export function isComplex(z) {
|
|||||||
return z && typeof z === 'object' && 're' in z && 'im' in z
|
return z && typeof z === 'object' && 're' in z && 'im' in z
|
||||||
}
|
}
|
||||||
|
|
||||||
typed.addType({name: 'Complex', test: isComplex})
|
export const Types = {
|
||||||
typed.addConversion({
|
Complex: {
|
||||||
from: 'number',
|
test: isComplex,
|
||||||
to: 'Complex',
|
number: x => ({re: x, im: 0}),
|
||||||
convert: x => ({re: x, im: 0})
|
bigint: x => ({re: x, im: 0n})
|
||||||
})
|
}
|
||||||
/* Pleasantly enough, it is OK to add this conversion even if there is no
|
}
|
||||||
* type 'bigint' defined, so everything should Just Work.
|
|
||||||
*/
|
|
||||||
typed.addConversion({
|
|
||||||
from: 'bigint',
|
|
||||||
to: 'Complex',
|
|
||||||
convert: x => ({re: x, im: 0n})
|
|
||||||
})
|
|
||||||
|
|
||||||
/* test if an entity is Complex<number>, so to speak: */
|
/* test if an entity is Complex<number>, so to speak: */
|
||||||
export function numComplex(z) {
|
export function numComplex(z) {
|
10
src/complex/add.mjs
Normal file
10
src/complex/add.mjs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export {Types} from './Types/Complex.mjs'
|
||||||
|
|
||||||
|
export const add = {
|
||||||
|
'...Complex': [['self'], ref => addends => {
|
||||||
|
if (addends.length === 0) return {re:0, im:0}
|
||||||
|
const seed = addends.shift()
|
||||||
|
return addends.reduce((w,z) =>
|
||||||
|
({re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}), seed)
|
||||||
|
}]
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export {Types} from './Types/Complex.mjs'
|
||||||
export {complex} from './complex.mjs'
|
export {complex} from './complex.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {negate} from './negate.mjs'
|
export {negate} from './negate.mjs'
|
@ -1,4 +1,4 @@
|
|||||||
import './types/Complex.mjs'
|
export {Types} from './Types/Complex.mjs'
|
||||||
|
|
||||||
export const complex = {
|
export const complex = {
|
||||||
/* Very permissive for sake of proof-of-concept; would be better to
|
/* Very permissive for sake of proof-of-concept; would be better to
|
@ -1,4 +1,3 @@
|
|||||||
import './types/Complex.mjs'
|
|
||||||
import * as complex from './complex.mjs'
|
import * as complex from './complex.mjs'
|
||||||
|
|
||||||
/* Add all the complex implementations for functions already
|
/* Add all the complex implementations for functions already
|
7
src/complex/negate.mjs
Normal file
7
src/complex/negate.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export {Types} from './Types/Complex.mjs'
|
||||||
|
|
||||||
|
export const negate = {
|
||||||
|
Complex: [['self'], ref => z => {
|
||||||
|
return {re: ref.self(z.re), im: ref.self(z.im)}
|
||||||
|
}]
|
||||||
|
}
|
@ -6,6 +6,10 @@ export default class PocomathInstance {
|
|||||||
this.name = name
|
this.name = name
|
||||||
this._imps = {}
|
this._imps = {}
|
||||||
this._affects = {}
|
this._affects = {}
|
||||||
|
this._typed = typed.create()
|
||||||
|
this._typed.clear()
|
||||||
|
// Convenient hack for now, would remove when a real string type is added:
|
||||||
|
this._typed.addTypes([{name: 'string', test: s => typeof s === 'string'}])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +33,14 @@ export default class PocomathInstance {
|
|||||||
* by the signature and returning the value. Otherwise, it should be
|
* by the signature and returning the value. Otherwise, it should be
|
||||||
* a function taking an object with the dependency lists as keys and the
|
* a function taking an object with the dependency lists as keys and the
|
||||||
* requested functions as values, to a function taking the arguments
|
* requested functions as values, to a function taking the arguments
|
||||||
* specified by the signature and returning the value
|
* specified by the signature and returning the value.
|
||||||
|
*
|
||||||
|
* Note that the "operation" named `Types` is special: it gives
|
||||||
|
* types that must be installed in the instance. In this case, the keys
|
||||||
|
* are type names, and the values are objects with a property 'test'
|
||||||
|
* giving the predicate for the type, and properties for each type that can
|
||||||
|
* be converted **to** this type, giving the corresponding conversion
|
||||||
|
* function.
|
||||||
*/
|
*/
|
||||||
install(ops) {
|
install(ops) {
|
||||||
for (const key in ops) this._installOp(key, ops[key])
|
for (const key in ops) this._installOp(key, ops[key])
|
||||||
@ -47,7 +58,7 @@ export default class PocomathInstance {
|
|||||||
`Conflicting definitions of ${signature} for ${name}`)
|
`Conflicting definitions of ${signature} for ${name}`)
|
||||||
} else {
|
} else {
|
||||||
opImps[signature] = implementations[signature]
|
opImps[signature] = implementations[signature]
|
||||||
for (const dep of implementations[signature][0]) {
|
for (const dep of implementations[signature][0] || []) {
|
||||||
const depname = dep.split('(', 1)[0]
|
const depname = dep.split('(', 1)[0]
|
||||||
if (depname === 'self') continue
|
if (depname === 'self') continue
|
||||||
if (!(depname in this._affects)) {
|
if (!(depname in this._affects)) {
|
||||||
@ -87,6 +98,7 @@ export default class PocomathInstance {
|
|||||||
if (!imps || Object.keys(imps).length === 0) {
|
if (!imps || Object.keys(imps).length === 0) {
|
||||||
throw new SyntaxError(`No implementations for ${name}`)
|
throw new SyntaxError(`No implementations for ${name}`)
|
||||||
}
|
}
|
||||||
|
this._ensureTypes()
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const signature in imps) {
|
for (const signature in imps) {
|
||||||
const [deps, imp] = imps[signature]
|
const [deps, imp] = imps[signature]
|
||||||
@ -107,7 +119,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (self_referential) {
|
if (self_referential) {
|
||||||
tf_imps[signature] = typed.referToSelf(self => {
|
tf_imps[signature] = this._typed.referToSelf(self => {
|
||||||
refs.self = self
|
refs.self = self
|
||||||
return imp(refs)
|
return imp(refs)
|
||||||
})
|
})
|
||||||
@ -116,7 +128,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const tf = typed(name, tf_imps)
|
const tf = this._typed(name, tf_imps)
|
||||||
this[name] = tf
|
this[name] = tf
|
||||||
return tf
|
return tf
|
||||||
}
|
}
|
||||||
@ -127,7 +139,40 @@ export default class PocomathInstance {
|
|||||||
*/
|
*/
|
||||||
_ensureBundle(name) {
|
_ensureBundle(name) {
|
||||||
const maybe = this[name]
|
const maybe = this[name]
|
||||||
if (typed.isTypedFunction(maybe)) return maybe
|
if (this._typed.isTypedFunction(maybe)) return maybe
|
||||||
return this._bundle(name)
|
return this._bundle(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that all of the requested types and conversions are actually
|
||||||
|
* in the typed-function universe:
|
||||||
|
*/
|
||||||
|
_ensureTypes() {
|
||||||
|
const newTypes = []
|
||||||
|
const newTypeSet = new Set()
|
||||||
|
const knownTypeSet = new Set()
|
||||||
|
const conversions = []
|
||||||
|
const typeSpec = this._imps.Types
|
||||||
|
for (const name in this._imps.Types) {
|
||||||
|
knownTypeSet.add(name)
|
||||||
|
for (const from in typeSpec[name]) {
|
||||||
|
if (from === 'test') continue;
|
||||||
|
conversions.push(
|
||||||
|
{from, to: name, convert: typeSpec[name][from]})
|
||||||
|
}
|
||||||
|
try { // Hack: work around typed-function #154
|
||||||
|
this._typed._findType(name)
|
||||||
|
} catch {
|
||||||
|
newTypeSet.add(name)
|
||||||
|
newTypes.push({name, test: typeSpec[name].test})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._typed.addTypes(newTypes)
|
||||||
|
const newConversions = conversions.filter(
|
||||||
|
item => (newTypeSet.has(item.from) || newTypeSet.has(item.to)) &&
|
||||||
|
knownTypeSet.has(item.from) && knownTypeSet.has(item.to)
|
||||||
|
)
|
||||||
|
this._typed.addConversions(newConversions)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
7
src/number/Types/number.mjs
Normal file
7
src/number/Types/number.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const Types = {
|
||||||
|
number: {
|
||||||
|
test: n => typeof n === 'number',
|
||||||
|
string: s => +s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
|||||||
|
export {Types} from './Types/number.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {
|
||||||
'...number': [[], addends => addends.reduce((x,y) => x+y, 0)],
|
'...number': [[], addends => addends.reduce((x,y) => x+y, 0)],
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export {Types} from './Types/number.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {negate} from './negate.mjs'
|
export {negate} from './negate.mjs'
|
||||||
export {subtract} from '../generic/subtract.mjs'
|
export {subtract} from '../generic/subtract.mjs'
|
3
src/number/negate.mjs
Normal file
3
src/number/negate.mjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { Types } from './Types/number.mjs'
|
||||||
|
|
||||||
|
export const negate = {number: [[], n => -n]}
|
@ -1,5 +1,5 @@
|
|||||||
/* Core of pocomath: generates the default instance */
|
/* Core of pocomath: generates the default instance */
|
||||||
import PocomathInstance from './PocomathInstance.mjs'
|
import PocomathInstance from './core/PocomathInstance.mjs'
|
||||||
import * as numbers from './number/all.mjs'
|
import * as numbers from './number/all.mjs'
|
||||||
import * as bigints from './bigint/all.mjs'
|
import * as bigints from './bigint/all.mjs'
|
||||||
import * as complex from './complex/all.mjs'
|
import * as complex from './complex/all.mjs'
|
@ -1,5 +1,5 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import math from '../pocomath.mjs'
|
import math from '../src/pocomath.mjs'
|
||||||
|
|
||||||
describe('The default full pocomath instance "math"', () => {
|
describe('The default full pocomath instance "math"', () => {
|
||||||
it('can subtract numbers', () => {
|
it('can subtract numbers', () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import PocomathInstance from '../PocomathInstance.mjs'
|
import PocomathInstance from '../../src/core/PocomathInstance.mjs'
|
||||||
|
|
||||||
describe('PocomathInstance', () => {
|
describe('PocomathInstance', () => {
|
||||||
it('creates an instance that can define typed-functions', () => {
|
it('creates an instance that can define typed-functions', () => {
|
@ -1,26 +1,26 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import math from '../pocomath.mjs'
|
import math from '../src/pocomath.mjs'
|
||||||
import typed from 'typed-function'
|
import PocomathInstance from '../src/core/PocomathInstance.mjs'
|
||||||
import PocomathInstance from '../PocomathInstance.mjs'
|
import * as numbers from '../src/number/all.mjs'
|
||||||
import * as numbers from '../number/all.mjs'
|
import * as numberAdd from '../src/number/add.mjs'
|
||||||
import * as numberAdd from '../number/add.mjs'
|
import * as complex from '../src/complex/all.mjs'
|
||||||
import * as complex from '../complex/all.mjs'
|
import * as complexAdd from '../src/complex/add.mjs'
|
||||||
import * as complexAdd from '../complex/add.mjs'
|
import * as complexNegate from '../src/complex/negate.mjs'
|
||||||
import * as complexNegate from '../complex/negate.mjs'
|
import extendToComplex from '../src/complex/extendToComplex.mjs'
|
||||||
import extendToComplex from '../complex/extendToComplex.mjs'
|
|
||||||
|
|
||||||
const bw = new PocomathInstance('backwards')
|
const bw = new PocomathInstance('backwards')
|
||||||
describe('A custom instance', () => {
|
describe('A custom instance', () => {
|
||||||
it("works when partially assembled", () => {
|
it("works when partially assembled", () => {
|
||||||
bw.install(complex)
|
bw.install(complex)
|
||||||
assert.deepStrictEqual(bw.add(2, bw.complex(0, 3)), {re: 2, im: 3})
|
// Not much we can call without any number types:
|
||||||
assert.deepStrictEqual(bw.negate(2), bw.complex(-2,-0))
|
assert.deepStrictEqual(bw.complex(0, 3), {re: 0, im: 3})
|
||||||
assert.deepStrictEqual(bw.subtract(2, bw.complex(0, 3)), {re: 2, im: -3})
|
// Don't have a way to negate things, for example:
|
||||||
|
assert.throws(() => bw.negate(2), TypeError)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can be assembled in any order", () => {
|
it("can be assembled in any order", () => {
|
||||||
bw.install(numbers)
|
bw.install(numbers)
|
||||||
typed.addConversion({from: 'string', to: 'number', convert: x => +x})
|
bw.install({Types: {string: {test: s => typeof s === 'string'}}})
|
||||||
assert.strictEqual(bw.subtract(16, bw.add(3,4,2)), 7)
|
assert.strictEqual(bw.subtract(16, bw.add(3,4,2)), 7)
|
||||||
assert.strictEqual(bw.negate('8'), -8)
|
assert.strictEqual(bw.negate('8'), -8)
|
||||||
assert.deepStrictEqual(bw.add(bw.complex(1,3), 1), {re: 2, im: 3})
|
assert.deepStrictEqual(bw.add(bw.complex(1,3), 1), {re: 2, im: 3})
|
||||||
@ -36,7 +36,7 @@ describe('A custom instance', () => {
|
|||||||
assert.strictEqual(pm.subtract(5, 10), -5)
|
assert.strictEqual(pm.subtract(5, 10), -5)
|
||||||
pm.install(complexAdd)
|
pm.install(complexAdd)
|
||||||
pm.install(complexNegate)
|
pm.install(complexNegate)
|
||||||
// Should be enough to allow complex subtraction, as subtract is generic
|
// Should be enough to allow complex subtraction, as subtract is generic:
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
pm.subtract({re:5, im:0}, {re:10, im:1}), {re:-5, im: -1})
|
pm.subtract({re:5, im:0}, {re:10, im:1}), {re:-5, im: -1})
|
||||||
})
|
})
|
||||||
@ -45,8 +45,9 @@ describe('A custom instance', () => {
|
|||||||
const cherry = new PocomathInstance('cherry')
|
const cherry = new PocomathInstance('cherry')
|
||||||
cherry.install(numberAdd)
|
cherry.install(numberAdd)
|
||||||
await extendToComplex(cherry)
|
await extendToComplex(cherry)
|
||||||
// Now we have an instance that supports addition for number and complex
|
/* Now we have an instance that supports addition for number and complex
|
||||||
// and little else:
|
and little else:
|
||||||
|
*/
|
||||||
assert.strictEqual(cherry.add(3, 4, 2), 9)
|
assert.strictEqual(cherry.add(3, 4, 2), 9)
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
cherry.add(cherry.complex(3, 3), 4, cherry.complex(2, 2)),
|
cherry.add(cherry.complex(3, 3), 4, cherry.complex(2, 2)),
|
||||||
|
Loading…
Reference in New Issue
Block a user