refactor: Simpler merging mechanism #33
@ -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