refactor: Simpler merging mechanism
Merging of Pocomath modules is eased by allowing one PocomathInstance to be merged into another. That allows types, for example, to be exported as a PocomathInstance (so there is no need for a special identifier convention for types; they can be directly added with an installType method). Also, larger modules can just be exported as an instance, since there is more flexibility and more checking in merging PocomathInstances than raw modules.
This commit is contained in:
parent
58fa661a2d
commit
d9d7af961f
@ -1,4 +1,7 @@
|
|||||||
export const Type_bigint = {
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
const BigInt = new PocomathInstance('BigInt')
|
||||||
|
BigInt.installType('bigint', {
|
||||||
before: ['Complex'],
|
before: ['Complex'],
|
||||||
test: b => typeof b === 'bigint'
|
test: b => typeof b === 'bigint'
|
||||||
}
|
})
|
||||||
|
export {BigInt}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
export * from './native.mjs'
|
import PocomathInstance from '../core/PocomathInstance.mjs'
|
||||||
export * from '../generic/arithmetic.mjs'
|
import * as bigints from './native.mjs'
|
||||||
|
import * as generic from '../generic/arithmetic.mjs'
|
||||||
|
|
||||||
// resolve the conflicts
|
export default PocomathInstance.merge('bigint', bigints, generic)
|
||||||
export {divide} from './divide.mjs'
|
|
||||||
export {multiply} from './multiply.mjs'
|
|
||||||
export {sign} from './sign.mjs'
|
|
||||||
export {sqrt} from './sqrt.mjs'
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
|
||||||
/* 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).
|
||||||
@ -6,11 +8,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Type_Complex = {
|
const Complex = new PocomathInstance('Complex')
|
||||||
|
Complex.installType('Complex', {
|
||||||
test: isComplex,
|
test: isComplex,
|
||||||
from: {
|
from: {
|
||||||
number: x => ({re: x, im: 0}),
|
number: x => ({re: x, im: 0}),
|
||||||
bigint: x => ({re: x, im: 0n})
|
bigint: x => ({re: x, im: 0n})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
export {Complex}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export * from '../generic/arithmetic.mjs'
|
import PocomathInstance from '../core/PocomathInstance.mjs'
|
||||||
export * from './native.mjs'
|
import * as complexes from './native.mjs'
|
||||||
|
import * as generic from '../generic/arithmetic.mjs'
|
||||||
|
|
||||||
// resolve the conflicts
|
export default PocomathInstance.merge('complex', complexes, generic)
|
||||||
export {sqrt} from './sqrt.mjs'
|
|
||||||
|
@ -35,7 +35,6 @@ export const sqrt = {
|
|||||||
const reSign = sign(z.re)
|
const reSign = sign(z.re)
|
||||||
if (imSign === imZero && reSign === reOne) return self(z.re)
|
if (imSign === imZero && reSign === reOne) return self(z.re)
|
||||||
const reTwo = add(reOne, reOne)
|
const reTwo = add(reOne, reOne)
|
||||||
const partial = add(abs(z), z.re)
|
|
||||||
return complex(
|
return complex(
|
||||||
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
|
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
|
||||||
self(divide(subtract(abs(z),z.re), reTwo))
|
self(divide(subtract(abs(z),z.re), reTwo))
|
||||||
|
@ -3,13 +3,20 @@ import typed from 'typed-function'
|
|||||||
import dependencyExtractor from './dependencyExtractor.mjs'
|
import dependencyExtractor from './dependencyExtractor.mjs'
|
||||||
import {subsetOfKeys, typesOfSignature} from './utils.mjs'
|
import {subsetOfKeys, typesOfSignature} from './utils.mjs'
|
||||||
|
|
||||||
|
const anySpec = {} // fixed dummy specification of 'any' type
|
||||||
|
|
||||||
export default class PocomathInstance {
|
export default class PocomathInstance {
|
||||||
/* Disallowed names for ops; beware, this is slightly non-DRY
|
/* Disallowed names for ops; beware, this is slightly non-DRY
|
||||||
* in that if a new top-level PocomathInstance method is added, its name
|
* in that if a new top-level PocomathInstance method is added, its name
|
||||||
* must be added to this list.
|
* must be added to this list.
|
||||||
*/
|
*/
|
||||||
static reserved = new Set([
|
static reserved = new Set([
|
||||||
'config', 'importDependencies', 'install', 'name', 'Types'])
|
'config',
|
||||||
|
'importDependencies',
|
||||||
|
'install',
|
||||||
|
'installType',
|
||||||
|
'name',
|
||||||
|
'Types'])
|
||||||
|
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.name = name
|
this.name = name
|
||||||
@ -17,7 +24,7 @@ export default class PocomathInstance {
|
|||||||
this._affects = {}
|
this._affects = {}
|
||||||
this._typed = typed.create()
|
this._typed = typed.create()
|
||||||
this._typed.clear()
|
this._typed.clear()
|
||||||
this.Types = {any: {}} // dummy entry to track the default 'any' type
|
this.Types = {any: anySpec} // dummy entry to track the default 'any' type
|
||||||
this._doomed = new Set() // for detecting circular reference
|
this._doomed = new Set() // for detecting circular reference
|
||||||
this._config = {predictable: false}
|
this._config = {predictable: false}
|
||||||
const self = this
|
const self = this
|
||||||
@ -36,9 +43,19 @@ export default class PocomathInstance {
|
|||||||
/**
|
/**
|
||||||
* (Partially) define one or more operations of the instance:
|
* (Partially) define one or more operations of the instance:
|
||||||
*
|
*
|
||||||
* @param {Object<string, Object<Signature, ({deps})=> implementation>>} ops
|
* The sole parameter can be another Pocomath instance, in which case all
|
||||||
|
* of the types and operations of the other instance are installed in this
|
||||||
|
* one, or it can be a plain object as described below.
|
||||||
|
*
|
||||||
|
* @param {Object<string,
|
||||||
|
* PocomathInstance
|
||||||
|
* | Object<Signature, ({deps})=> implementation>>} ops
|
||||||
* The only parameter ops gives the semantics of the operations to install.
|
* The only parameter ops gives the semantics of the operations to install.
|
||||||
* The keys are operation names. The value for a key is an object
|
* The keys are operation names. The value for a key could be
|
||||||
|
* a PocomathInstance, in which case it is simply merged into this
|
||||||
|
* instance.
|
||||||
|
*
|
||||||
|
* Otherwise, ops must be an object
|
||||||
* mapping each desired (typed-function) signature to a function taking
|
* mapping each desired (typed-function) signature to a function taking
|
||||||
* a dependency object to an implementation.
|
* a dependency object to an implementation.
|
||||||
*
|
*
|
||||||
@ -63,29 +80,55 @@ export default class PocomathInstance {
|
|||||||
* with the signature in parentheses, e.g. `add(number,number)` to
|
* with the signature in parentheses, e.g. `add(number,number)` to
|
||||||
* refer to just adding two numbers. In this case, it is of course
|
* refer to just adding two numbers. In this case, it is of course
|
||||||
* necessary to specify an alias to be able to refer to the supplied
|
* necessary to specify an alias to be able to refer to the supplied
|
||||||
* operation in the body of the implementation. [NOTE: this signature-
|
* operation in the body of the implementation.
|
||||||
* specific reference is not yet implemented.]
|
|
||||||
*
|
|
||||||
* Note that any "operation" whose name begins with `Type_` is special:
|
|
||||||
* it defines a types that must be installed in the instance.
|
|
||||||
* The remainder of the "operation" name following the `_` is the
|
|
||||||
* name of the type. The value of the "operation" should be a plain
|
|
||||||
* object with the following properties:
|
|
||||||
*
|
|
||||||
* - test: the predicate for the type
|
|
||||||
* - from: a plain object mapping the names of types that can be converted
|
|
||||||
* **to** this type to the corresponding conversion functions
|
|
||||||
* - before: [optional] a list of types this should be added
|
|
||||||
* before, in priority order
|
|
||||||
*/
|
*/
|
||||||
install(ops) {
|
install(ops) {
|
||||||
|
if (ops instanceof PocomathInstance) {
|
||||||
|
return _installInstance(ops)
|
||||||
|
}
|
||||||
|
/* Standardize the format of all implementations, weeding out
|
||||||
|
* any other instances as we go
|
||||||
|
*/
|
||||||
|
const stdFunctions = {}
|
||||||
for (const [item, spec] of Object.entries(ops)) {
|
for (const [item, spec] of Object.entries(ops)) {
|
||||||
if (item.slice(0,5) === 'Type_') {
|
if (spec instanceof PocomathInstance) {
|
||||||
this._installType(item.slice(5), spec)
|
this._installInstance(spec)
|
||||||
} else {
|
} else {
|
||||||
this._installOp(item, spec)
|
if (item.charAt(0) === '_') {
|
||||||
|
throw new SyntaxError(
|
||||||
|
`Pocomath: Cannot install ${item}, `
|
||||||
|
+ 'initial _ reserved for internal use.')
|
||||||
|
}
|
||||||
|
if (PocomathInstance.reserved.has(item)) {
|
||||||
|
throw new SyntaxError(
|
||||||
|
`Pocomath: reserved function '${item}' cannot be modified.`)
|
||||||
|
}
|
||||||
|
const stdimps = {}
|
||||||
|
for (const [signature, does] of Object.entries(spec)) {
|
||||||
|
const uses = new Set()
|
||||||
|
does(dependencyExtractor(uses))
|
||||||
|
stdimps[signature] = {uses, does}
|
||||||
|
}
|
||||||
|
stdFunctions[item] = stdimps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this._installFunctions(stdFunctions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Merge any number of PocomathInstances or modules: */
|
||||||
|
static merge(name, ...pieces) {
|
||||||
|
const result = new PocomathInstance(name)
|
||||||
|
for (const piece of pieces) {
|
||||||
|
result.install(piece)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
_installInstance(other) {
|
||||||
|
for (const [type, spec] of Object.entries(other.Types)) {
|
||||||
|
this.installType(type, spec)
|
||||||
|
}
|
||||||
|
this._installFunctions(other._imps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,10 +170,29 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Used internally by install, see the documentation there.
|
/* Used to install a type in a PocomathInstance.
|
||||||
* Note that unlike _installOp below, we can do this immediately
|
*
|
||||||
|
* @param {string} name The name of the type
|
||||||
|
* @param {{test: any => bool, // the predicate for the type
|
||||||
|
* from: Record<string, <that type> => <type name>> // conversions
|
||||||
|
* before: string[] // lower priority types
|
||||||
|
* }} specification
|
||||||
|
*
|
||||||
|
* The second parameter of this function specifies the structure of the
|
||||||
|
* type via a plain
|
||||||
|
* object with the following properties:
|
||||||
|
*
|
||||||
|
* - test: the predicate for the type
|
||||||
|
* - from: a plain object mapping the names of types that can be converted
|
||||||
|
* **to** this type to the corresponding conversion functions
|
||||||
|
* - before: [optional] a list of types this should be added
|
||||||
|
* before, in priority order
|
||||||
*/
|
*/
|
||||||
_installType(type, spec) {
|
/*
|
||||||
|
* Implementation note: unlike _installFunctions below, we can make
|
||||||
|
* the corresponding changes to the _typed object immediately
|
||||||
|
*/
|
||||||
|
installType(type, spec) {
|
||||||
if (type in this.Types) {
|
if (type in this.Types) {
|
||||||
if (spec !== this.Types[type]) {
|
if (spec !== this.Types[type]) {
|
||||||
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
||||||
@ -165,36 +227,27 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Used internally by install, see the documentation there */
|
/* Used internally by install, see the documentation there */
|
||||||
_installOp(name, implementations) {
|
_installFunctions(functions) {
|
||||||
if (name.charAt(0) === '_') {
|
for (const [name, spec] of Object.entries(functions)) {
|
||||||
throw new SyntaxError(
|
// new implementations, so set the op up to lazily recreate itself
|
||||||
`Pocomath: Cannot install ${name}, `
|
this._invalidate(name)
|
||||||
+ 'initial _ reserved for internal use.')
|
const opImps = this._imps[name]
|
||||||
}
|
for (const [signature, behavior] of Object.entries(spec)) {
|
||||||
if (PocomathInstance.reserved.has(name)) {
|
if (signature in opImps) {
|
||||||
throw new SyntaxError(
|
if (behavior.does !== opImps[signature].does) {
|
||||||
`Pocomath: the meaning of function '${name}' cannot be modified.`)
|
throw new SyntaxError(
|
||||||
}
|
`Conflicting definitions of ${signature} for ${name}`)
|
||||||
// new implementations, so set the op up to lazily recreate itself
|
}
|
||||||
this._invalidate(name)
|
} else {
|
||||||
const opImps = this._imps[name]
|
opImps[signature] = behavior
|
||||||
for (const [signature, does] of Object.entries(implementations)) {
|
for (const dep of behavior.uses) {
|
||||||
if (signature in opImps) {
|
const depname = dep.split('(', 1)[0]
|
||||||
if (does !== opImps[signature].does) {
|
if (depname === 'self') continue
|
||||||
throw new SyntaxError(
|
this._addAffect(depname, name)
|
||||||
`Conflicting definitions of ${signature} for ${name}`)
|
}
|
||||||
}
|
for (const type of typesOfSignature(signature)) {
|
||||||
} else {
|
this._addAffect(':' + type, name)
|
||||||
const uses = new Set()
|
}
|
||||||
does(dependencyExtractor(uses))
|
|
||||||
opImps[signature] = {uses, does}
|
|
||||||
for (const dep of uses) {
|
|
||||||
const depname = dep.split('(', 1)[0]
|
|
||||||
if (depname === 'self') continue
|
|
||||||
this._addAffect(depname, name)
|
|
||||||
}
|
|
||||||
for (const type of typesOfSignature(signature)) {
|
|
||||||
this._addAffect(':' + type, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
export const Type_undefined = {test: u => u === undefined}
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
const Undefined = new PocomathInstance('Undefined')
|
||||||
|
Undefined.installType('undefined', {test: u => u === undefined})
|
||||||
|
export {Undefined}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export const Type_number = {
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
const Number = new PocomathInstance('Number')
|
||||||
|
Number.installType('number', {
|
||||||
before: ['Complex'],
|
before: ['Complex'],
|
||||||
test: n => typeof n === 'number',
|
test: n => typeof n === 'number',
|
||||||
from: {string: s => +s}
|
from: {string: s => +s}
|
||||||
}
|
})
|
||||||
|
export {Number}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export * from '../generic/arithmetic.mjs'
|
import PocomathInstance from '../core/PocomathInstance.mjs'
|
||||||
export * from './native.mjs'
|
import * as numbers from './native.mjs'
|
||||||
|
import * as generic from '../generic/arithmetic.mjs'
|
||||||
|
|
||||||
|
export default PocomathInstance.merge('number', numbers, generic)
|
||||||
|
|
||||||
// resolve the conflicts
|
|
||||||
export {sqrt} from './sqrt.mjs'
|
|
||||||
export {multiply} from './multiply.mjs'
|
|
||||||
|
@ -5,10 +5,6 @@ import * as bigints from './bigint/native.mjs'
|
|||||||
import * as complex from './complex/native.mjs'
|
import * as complex from './complex/native.mjs'
|
||||||
import * as generic from './generic/all.mjs'
|
import * as generic from './generic/all.mjs'
|
||||||
|
|
||||||
const math = new PocomathInstance('math')
|
const math = PocomathInstance.merge('math', numbers, bigints, complex, generic)
|
||||||
math.install(numbers)
|
|
||||||
math.install(bigints)
|
|
||||||
math.install(complex)
|
|
||||||
math.install(generic)
|
|
||||||
|
|
||||||
export default math
|
export default math
|
||||||
|
@ -18,14 +18,14 @@ describe('The default full pocomath instance "math"', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can be extended', () => {
|
it('can be extended', () => {
|
||||||
|
math.installType('stringK', {
|
||||||
|
test: s => typeof s === 'string' && s.charAt(0) === 'K',
|
||||||
|
before: ['string']
|
||||||
|
})
|
||||||
math.install({
|
math.install({
|
||||||
add: {
|
add: {
|
||||||
'...stringK': () => addends => addends.reduce((x,y) => x+y, '')
|
'...stringK': () => addends => addends.reduce((x,y) => x+y, '')
|
||||||
},
|
},
|
||||||
Type_stringK: {
|
|
||||||
test: s => typeof s === 'string' && s.charAt(0) === 'K',
|
|
||||||
before: ['string']
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
assert.strictEqual(math.add('Kilroy','K is here'), 'KilroyK is here')
|
assert.strictEqual(math.add('Kilroy','K is here'), 'KilroyK is here')
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ describe('A custom instance', () => {
|
|||||||
|
|
||||||
it("can be assembled in any order", () => {
|
it("can be assembled in any order", () => {
|
||||||
bw.install(numbers)
|
bw.install(numbers)
|
||||||
bw.install({Type_string: {test: s => typeof s === 'string'}})
|
bw.installType('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})
|
||||||
|
Loading…
Reference in New Issue
Block a user