feat: Allow self-reference in implementations #4

Merged
glen merged 2 commits from self_reference into main 2022-07-19 18:54:23 +00:00
13 changed files with 99 additions and 21 deletions
Showing only changes of commit 66cbccfbbe - Show all commits

View File

@ -41,7 +41,7 @@ export default class PocomathInstance {
const opImps = this._imps[name] const opImps = this._imps[name]
for (const signature in implementations) { for (const signature in implementations) {
if (signature in opImps) { if (signature in opImps) {
if (implemenatations[signature] === opImps[signature]) continue if (implementations[signature] === opImps[signature]) continue
throw new SyntaxError( throw new SyntaxError(
`Conflicting definitions of ${signature} for ${name}`) `Conflicting definitions of ${signature} for ${name}`)
} else { } else {
@ -80,18 +80,26 @@ export default class PocomathInstance {
tf_imps[signature] = imp tf_imps[signature] = imp
} else { } else {
const refs = {} const refs = {}
let self_referential = false
for (const dep of deps) { for (const dep of deps) {
// TODO: handle self dependencies
if (dep.slice(0,4) === 'self') {
throw new Error('self-reference unimplemented')
}
// TODO: handle signature-specific dependencies // TODO: handle signature-specific dependencies
if (dep.includes('(')) { if (dep.includes('(')) {
throw new Error('signature specific reference unimplemented') throw new Error('signature specific reference unimplemented')
} }
refs[dep] = this._ensureBundle(dep) // just assume acyclic for now if (dep === 'self') {
self_referential = true
} else {
refs[dep] = this._ensureBundle(dep) // assume acyclic for now
}
}
if (self_referential) {
tf_imps[signature] = typed.referToSelf(self => {
refs.self = self
return imp(refs)
})
} else {
tf_imps[signature] = imp(refs)
} }
tf_imps[signature] = imp(refs)
} }
} }
const tf = typed(name, tf_imps) const tf = typed(name, tf_imps)

3
bigint/BigInt.mjs Normal file
View File

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

4
bigint/add.mjs Normal file
View File

@ -0,0 +1,4 @@
import './BigInt.mjs'
export const add = {
'...bigint': [[], addends => addends.reduce((x,y) => x+y, 0n)],
}

3
bigint/all.mjs Normal file
View File

@ -0,0 +1,3 @@
export {add} from './add.mjs'
export {negate} from './negate.mjs'
export {subtract} from '../generic/subtract.mjs'

2
bigint/negate.mjs Normal file
View File

@ -0,0 +1,2 @@
import './BigInt.mjs'
export const negate = {bigint: [[], b => -b ]}

View File

@ -1,14 +1,29 @@
import typed from 'typed-function' import typed from 'typed-function'
/* Use a plain object with keys re and im for a complex */ /* Use a plain object with keys re and im for a complex; note the components
typed.addType({ * can be any type (for this proof-of-concept; in reality we'd want to
name: 'Complex', * insist on some numeric or scalar supertype).
test: z => z && typeof z === 'object' && 're' in z && 'im' in z */
}) export function isComplex(z) {
return z && typeof z === 'object' && 're' in z && 'im' in z
}
typed.addType({name: 'Complex', test: isComplex})
typed.addConversion({ typed.addConversion({
from: 'number', from: 'number',
to: 'Complex', to: 'Complex',
convert: x => ({re: x, im: 0}) convert: x => ({re: x, im: 0})
}) })
/* 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: */
export function numComplex(z) {
return isComplex(z) && typeof z.re === 'number' && typeof z.im === 'number'
}

View File

@ -1,12 +1,15 @@
import './Complex.mjs' import {numComplex} from './Complex.mjs'
export const add = { export const add = {
'...Complex': [[], addends => { '...Complex': [['self'], ref => addends => {
const sum = {re: 0, im: 0} if (addends.length === 0) return {re:0, im:0}
addends.forEach(addend => { const seed = addends.shift()
sum.re += addend.re return addends.reduce((w,z) => {
sum.im += addend.im /* Need a "base case" to avoid infinite self-reference loops */
}) if (numComplex(z) && numComplex(w)) {
return sum 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,2 +1,4 @@
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 {subtract} from '../generic/subtract.mjs'

View File

@ -1,6 +1,11 @@
import './Complex.mjs' import './Complex.mjs'
export const complex = { export const complex = {
'number, number': [[], (x, y) => ({re: x, im: y})], /* Very permissive for sake of proof-of-concept; would be better to
* have a numeric/scalar type, e.g. by implementing subtypes in
* typed-function
*/
'any, any': [[], (x, y) => ({re: x, im: y})],
/* Take advantage of conversions in typed-function */
Complex: [[], z => z] Complex: [[], z => z]
} }

4
complex/negate.mjs Normal file
View File

@ -0,0 +1,4 @@
import './Complex.mjs'
export const negate = {
Complex: [['self'], ref => z => ({re: ref.self(z.re), im: ref.self(z.im)})]
}

View File

@ -1,10 +1,12 @@
/* Core of pocomath: generates the default instance */ /* Core of pocomath: generates the default instance */
import PocomathInstance from './PocomathInstance.mjs' import PocomathInstance from './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 complex from './complex/all.mjs' import * as complex from './complex/all.mjs'
const math = new PocomathInstance('math') const math = new PocomathInstance('math')
math.install(numbers) math.install(numbers)
math.install(bigints)
math.install(complex) math.install(complex)
export default math export default math

View File

@ -29,5 +29,27 @@ describe('The default full pocomath instance "math"', () => {
assert.deepStrictEqual(math.complex(2,3), norm13) assert.deepStrictEqual(math.complex(2,3), norm13)
assert.deepStrictEqual(math.complex(2), math.complex(2,0)) assert.deepStrictEqual(math.complex(2), math.complex(2,0))
assert.deepStrictEqual(math.add(2, math.complex(0,3)), norm13) assert.deepStrictEqual(math.add(2, math.complex(0,3)), norm13)
assert.deepStrictEqual(
math.subtract(16, math.add(3, math.complex(0,4), 2)),
math.complex(11, -4))
assert.strictEqual(math.negate(math.complex(3, 8)).im, -8)
})
it('handles bigints', () => {
assert.strictEqual(math.negate(5n), -5n)
assert.strictEqual(math.subtract(12n, 5n), 7n)
assert.strictEqual(math.add(15n, 25n, 35n), 75n)
assert.strictEqual(math.add(10n, math.negate(3n)), 7n)
})
it('handles Gaussian integers', () => {
const norm13n = {re: 2n, im: 3n}
assert.deepStrictEqual(math.complex(2n,3n), norm13n)
assert.deepStrictEqual(math.complex(2n), math.complex(2n, 0n))
assert.deepStrictEqual(math.add(2n, math.complex(0n, 3n)), norm13n)
assert.deepStrictEqual(
math.subtract(16n, math.add(3n, math.complex(0n,4n), 2n)),
math.complex(11n, -4n))
assert.strictEqual(math.negate(math.complex(3n, 8n)).im, -8n)
}) })
}) })

View File

@ -1,4 +1,5 @@
import assert from 'assert' import assert from 'assert'
import math from '../pocomath.mjs'
import typed from 'typed-function' import typed from 'typed-function'
import PocomathInstance from '../PocomathInstance.mjs' import PocomathInstance from '../PocomathInstance.mjs'
import * as numbers from '../number/all.mjs' import * as numbers from '../number/all.mjs'
@ -17,5 +18,9 @@ describe('A custom instance', () => {
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})
assert.deepStrictEqual(
bw.subtract(16, bw.add(3, bw.complex(0,4), 2)),
math.complex(11, -4)) // note both instances coexist
assert.deepStrictEqual(bw.negate(math.complex(3, '8')).im, -8)
}) })
}) })