fix: Separate typed instance for each PocomathInstance #15

Merged
glen merged 3 commits from no_complex_base into main 2022-07-22 20:49:14 +00:00
25 changed files with 120 additions and 74 deletions

View File

@ -1,3 +0,0 @@
import typed from 'typed-function'
typed.addType({name: 'bigint', test: b => typeof b === 'bigint'})

View File

@ -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)
}]
}

View File

@ -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)}
}]
}

View File

@ -1,3 +0,0 @@
export const negate = {
number: [[], n => -n]
}

View File

@ -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',

View File

@ -0,0 +1,3 @@
export const Types = {
bigint: {test: b => typeof b === 'bigint'}
}

View File

@ -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)],
} }

View File

@ -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'

View File

@ -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 ]}

View File

@ -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
View 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)
}]
}

View File

@ -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'

View File

@ -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

View File

@ -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
View 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)}
}]
}

View File

@ -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)
}
} }

View File

@ -0,0 +1,7 @@
export const Types = {
number: {
test: n => typeof n === 'number',
string: s => +s
}
}

View File

@ -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)],
} }

View File

@ -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
View File

@ -0,0 +1,3 @@
export { Types } from './Types/number.mjs'
export const negate = {number: [[], n => -n]}

View File

@ -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'

View File

@ -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', () => {

View File

@ -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', () => {

View File

@ -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)),