Merge pull request 'feat: Implement signature-specifc reference' (#25) from specific_signature into main
Reviewed-on: #25
This commit is contained in:
commit
b21d2b59fa
5
src/complex/abs.mjs
Normal file
5
src/complex/abs.mjs
Normal file
@ -0,0 +1,5 @@
|
||||
export {Types} from './Types/Complex.mjs'
|
||||
|
||||
export const abs = {Complex: ({sqrt, add, multiply}) => z => {
|
||||
return sqrt(add(multiply(z.re, z.re), multiply(z.im, z.im)))
|
||||
}}
|
@ -1,5 +1,2 @@
|
||||
export {Types} from './Types/Complex.mjs'
|
||||
export {complex} from './complex.mjs'
|
||||
export {add} from './add.mjs'
|
||||
export {negate} from './negate.mjs'
|
||||
export {subtract} from '../generic/subtract.mjs'
|
||||
export * from './native.mjs'
|
||||
export * from '../generic/arithmetic.mjs'
|
||||
|
8
src/complex/native.mjs
Normal file
8
src/complex/native.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
export {Types} from './Types/Complex.mjs'
|
||||
|
||||
export {abs} from './abs.mjs'
|
||||
export {add} from './add.mjs'
|
||||
export {complex} from './complex.mjs'
|
||||
export {negate} from './negate.mjs'
|
||||
export {sqrt} from './sqrt.mjs'
|
||||
|
37
src/complex/sqrt.mjs
Normal file
37
src/complex/sqrt.mjs
Normal file
@ -0,0 +1,37 @@
|
||||
export { Types } from './Types/Complex.mjs'
|
||||
|
||||
export const sqrt = {
|
||||
Complex: ({
|
||||
config,
|
||||
complex,
|
||||
multiply,
|
||||
sign,
|
||||
self,
|
||||
divide,
|
||||
add,
|
||||
'abs(Complex)': abs,
|
||||
subtract
|
||||
}) => {
|
||||
if (config.predictable) {
|
||||
return z => {
|
||||
const imSign = sign(z.im)
|
||||
const reSign = sign(z.re)
|
||||
if (imSign === 0 && reSign === 1) return complex(self(z.re))
|
||||
return complex(
|
||||
multiply(sign(z.im), self(divide(add(abs(z),z.re), 2))),
|
||||
self(divide(subtract(abs(z),z.re), 2))
|
||||
)
|
||||
}
|
||||
}
|
||||
return z => {
|
||||
const imSign = sign(z.im)
|
||||
const reSign = sign(z.re)
|
||||
if (imSign === 0 && reSign === 1) return self(z.re)
|
||||
return complex(
|
||||
multiply(sign(z.im), self(divide(add(abs(z),z.re), 2))),
|
||||
self(divide(subtract(abs(z),z.re), 2))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ export default class PocomathInstance {
|
||||
* in that if a new top-level PocomathInstance method is added, its name
|
||||
* must be added to this list.
|
||||
*/
|
||||
static reserved = new Set(['install', 'importDependencies'])
|
||||
static reserved = new Set(['config', 'importDependencies', 'install', 'name'])
|
||||
|
||||
constructor(name) {
|
||||
this.name = name
|
||||
@ -17,6 +17,19 @@ export default class PocomathInstance {
|
||||
this._typed = typed.create()
|
||||
this._typed.clear()
|
||||
this.Types = {any: {}} // dummy entry to track the default 'any' type
|
||||
this._doomed = new Set() // for detecting circular reference
|
||||
this._config = {predictable: false}
|
||||
const self = this
|
||||
this.config = new Proxy(this._config, {
|
||||
get: (target, property) => target[property],
|
||||
set: (target, property, value) => {
|
||||
if (value !== target[property]) {
|
||||
target[property] = value
|
||||
self._invalidateDependents('config')
|
||||
}
|
||||
return true // successful
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,6 +93,8 @@ export default class PocomathInstance {
|
||||
* @param {string[]} types A list of type names
|
||||
*/
|
||||
async importDependencies(types) {
|
||||
const typeSet = new Set(types)
|
||||
typeSet.add('generic')
|
||||
const doneSet = new Set(['self']) // nothing to do for self dependencies
|
||||
while (true) {
|
||||
const requiredSet = new Set()
|
||||
@ -96,7 +111,7 @@ export default class PocomathInstance {
|
||||
}
|
||||
if (requiredSet.size === 0) break
|
||||
for (const name of requiredSet) {
|
||||
for (const type of types) {
|
||||
for (const type of typeSet) {
|
||||
try {
|
||||
const modName = `../${type}/${name}.mjs`
|
||||
const mod = await import(modName)
|
||||
@ -145,7 +160,8 @@ export default class PocomathInstance {
|
||||
}
|
||||
}
|
||||
this.Types[type] = spec
|
||||
this._invalidate(':' + type) // rebundle anything that uses the new type
|
||||
// rebundle anything that uses the new type:
|
||||
this._invalidateDependents(':' + type)
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,14 +214,27 @@ export default class PocomathInstance {
|
||||
* and if it has no implementations so far, set them up.
|
||||
*/
|
||||
_invalidate(name) {
|
||||
if (this._doomed.has(name)) {
|
||||
/* In the midst of a circular invalidation, so do nothing */
|
||||
return
|
||||
}
|
||||
if (!(name in this._imps)) {
|
||||
this._imps[name] = {}
|
||||
}
|
||||
this._doomed.add(name)
|
||||
this._invalidateDependents(name)
|
||||
this._doomed.delete(name)
|
||||
const self = this
|
||||
Object.defineProperty(this, name, {
|
||||
configurable: true,
|
||||
get: () => self._bundle(name)
|
||||
})
|
||||
if (!(name in this._imps)) {
|
||||
this._imps[name] = {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all the dependents of a given property of the instance
|
||||
*/
|
||||
_invalidateDependents(name) {
|
||||
if (name in this._affects) {
|
||||
for (const ancestor of this._affects[name]) {
|
||||
this._invalidate(ancestor)
|
||||
@ -229,29 +258,69 @@ export default class PocomathInstance {
|
||||
`Every implementation for ${name} uses an undefined type;\n`
|
||||
+ ` signatures: ${Object.keys(imps)}`)
|
||||
}
|
||||
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
||||
const tf_imps = {}
|
||||
for (const [signature, {uses, does}] of usableEntries) {
|
||||
if (uses.length === 0) {
|
||||
tf_imps[signature] = does()
|
||||
} else {
|
||||
const refs = {}
|
||||
let self_referential = false
|
||||
let full_self_referential = false
|
||||
let part_self_references = []
|
||||
for (const dep of uses) {
|
||||
// TODO: handle signature-specific dependencies
|
||||
if (dep.includes('(')) {
|
||||
throw new Error('signature specific reference unimplemented')
|
||||
}
|
||||
if (dep === 'self') {
|
||||
self_referential = true
|
||||
const [func, needsig] = dep.split(/[()]/)
|
||||
if (func === 'self') {
|
||||
if (needsig) {
|
||||
if (full_self_referential) {
|
||||
throw new SyntaxError(
|
||||
'typed-function does not support mixed full and '
|
||||
+ 'partial self-reference')
|
||||
}
|
||||
if (subsetOfKeys(typesOfSignature(needsig), this.Types)) {
|
||||
part_self_references.push(needsig)
|
||||
}
|
||||
} else {
|
||||
if (part_self_references.length) {
|
||||
throw new SyntaxError(
|
||||
'typed-function does not support mixed full and '
|
||||
+ 'partial self-reference')
|
||||
}
|
||||
full_self_referential = true
|
||||
}
|
||||
} else {
|
||||
refs[dep] = this[dep] // assume acyclic for now
|
||||
if (this[func] === 'limbo') {
|
||||
/* We are in the midst of bundling func, so have to use
|
||||
* an indirect reference to func. And given that, there's
|
||||
* really no helpful way to extract a specific signature
|
||||
*/
|
||||
const self = this
|
||||
refs[dep] = function () { // is this the most efficient?
|
||||
return self[func].apply(this, arguments)
|
||||
}
|
||||
} else {
|
||||
// can bundle up func, and grab its signature if need be
|
||||
let destination = this[func]
|
||||
if (needsig) {
|
||||
destination = this._typed.find(destination, needsig)
|
||||
}
|
||||
refs[dep] = destination
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self_referential) {
|
||||
if (full_self_referential) {
|
||||
tf_imps[signature] = this._typed.referToSelf(self => {
|
||||
refs.self = self
|
||||
return does(refs)
|
||||
})
|
||||
} else if (part_self_references.length) {
|
||||
tf_imps[signature] = this._typed.referTo(
|
||||
...part_self_references, (...impls) => {
|
||||
for (let i = 0; i < part_self_references.length; ++i) {
|
||||
refs[`self(${part_self_references[i]})`] = impls[i]
|
||||
}
|
||||
return does(refs)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
tf_imps[signature] = does(refs)
|
||||
}
|
||||
|
@ -4,6 +4,9 @@
|
||||
*/
|
||||
export default function dependencyExtractor(destinationSet) {
|
||||
return new Proxy({}, {
|
||||
get: (target, property) => { destinationSet.add(property) }
|
||||
get: (target, property) => {
|
||||
destinationSet.add(property)
|
||||
return {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
1
src/generic/all.mjs
Normal file
1
src/generic/all.mjs
Normal file
@ -0,0 +1 @@
|
||||
export * from './arithmetic.mjs'
|
3
src/generic/arithmetic.mjs
Normal file
3
src/generic/arithmetic.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
export {divide} from './divide.mjs'
|
||||
export {sign} from './sign.mjs'
|
||||
export {subtract} from './subtract.mjs'
|
4
src/generic/divide.mjs
Normal file
4
src/generic/divide.mjs
Normal file
@ -0,0 +1,4 @@
|
||||
export const divide = {
|
||||
'any,any': ({multiply, invert}) => (x, y) => multiply(x, invert(y))
|
||||
}
|
||||
|
6
src/generic/sign.mjs
Normal file
6
src/generic/sign.mjs
Normal file
@ -0,0 +1,6 @@
|
||||
export const sign = {
|
||||
any: ({negate, divide, abs}) => x => {
|
||||
if (x === negate(x)) return x // zero
|
||||
return divide(x, abs(x))
|
||||
}
|
||||
}
|
3
src/number/abs.mjs
Normal file
3
src/number/abs.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
export {Types} from './Types/number.mjs'
|
||||
|
||||
export const abs = {number: () => n => Math.abs(n)}
|
@ -1,4 +1,4 @@
|
||||
export {Types} from './Types/number.mjs'
|
||||
export {add} from './add.mjs'
|
||||
export {negate} from './negate.mjs'
|
||||
export {subtract} from '../generic/subtract.mjs'
|
||||
|
||||
export * from './native.mjs'
|
||||
export * from '../generic/arithmetic.mjs'
|
||||
|
3
src/number/invert.mjs
Normal file
3
src/number/invert.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
export {Types} from './Types/number.mjs'
|
||||
|
||||
export const invert = {number: () => n => 1/n}
|
5
src/number/multiply.mjs
Normal file
5
src/number/multiply.mjs
Normal file
@ -0,0 +1,5 @@
|
||||
export {Types} from './Types/number.mjs'
|
||||
|
||||
export const multiply = {
|
||||
'...number': () => multiplicands => multiplicands.reduce((x,y) => x*y, 1),
|
||||
}
|
4
src/number/native.js
Normal file
4
src/number/native.js
Normal file
@ -0,0 +1,4 @@
|
||||
export {Types} from './Types/number.mjs'
|
||||
export {add} from './add.mjs'
|
||||
export {negate} from './negate.mjs'
|
||||
export {sqrt} from './sqrt.mjs'
|
8
src/number/native.mjs
Normal file
8
src/number/native.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
export {Types} from './Types/number.mjs'
|
||||
|
||||
export {abs} from './abs.mjs'
|
||||
export {add} from './add.mjs'
|
||||
export {invert} from './invert.mjs'
|
||||
export {multiply} from './multiply.mjs'
|
||||
export {negate} from './negate.mjs'
|
||||
export {sqrt} from './sqrt.mjs'
|
14
src/number/sqrt.mjs
Normal file
14
src/number/sqrt.mjs
Normal file
@ -0,0 +1,14 @@
|
||||
export { Types } from './Types/number.mjs'
|
||||
|
||||
export const sqrt = {
|
||||
number: ({config, complex, 'self(Complex)': complexSqrt}) => {
|
||||
if (config.predictable || !complexSqrt) {
|
||||
return n => isNaN(n) ? NaN : Math.sqrt(n)
|
||||
}
|
||||
return n => {
|
||||
if (isNaN(n)) return NaN
|
||||
if (n >= 0) return Math.sqrt(n)
|
||||
return complexSqrt(complex(n))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
/* Core of pocomath: generates the default instance */
|
||||
import PocomathInstance from './core/PocomathInstance.mjs'
|
||||
import * as numbers from './number/all.mjs'
|
||||
import * as numbers from './number/native.mjs'
|
||||
import * as bigints from './bigint/all.mjs'
|
||||
import * as complex from './complex/all.mjs'
|
||||
import * as complex from './complex/native.mjs'
|
||||
import * as generic from './generic/all.mjs'
|
||||
|
||||
const math = new PocomathInstance('math')
|
||||
math.install(numbers)
|
||||
math.install(bigints)
|
||||
math.install(complex)
|
||||
math.install(generic)
|
||||
|
||||
export default math
|
||||
|
0
test/complex/##
Executable file
0
test/complex/##
Executable file
29
test/complex/_all.mjs
Normal file
29
test/complex/_all.mjs
Normal file
@ -0,0 +1,29 @@
|
||||
import assert from 'assert'
|
||||
import math from '../../src/pocomath.mjs'
|
||||
import PocomathInstance from '../../src/core/PocomathInstance.mjs'
|
||||
import * as complexSqrt from '../../src/complex/sqrt.mjs'
|
||||
|
||||
describe('complex', () => {
|
||||
it('supports sqrt', () => {
|
||||
assert.deepStrictEqual(math.sqrt(math.complex(1,0)), 1)
|
||||
assert.deepStrictEqual(
|
||||
math.sqrt(math.complex(0,1)),
|
||||
math.complex(math.sqrt(0.5), math.sqrt(0.5)))
|
||||
math.config.predictable = true
|
||||
assert.deepStrictEqual(math.sqrt(math.complex(1,0)), math.complex(1,0))
|
||||
assert.deepStrictEqual(
|
||||
math.sqrt(math.complex(0,1)),
|
||||
math.complex(math.sqrt(0.5), math.sqrt(0.5)))
|
||||
math.config.predictable = false
|
||||
})
|
||||
|
||||
it('can bundle sqrt', async function () {
|
||||
const ms = new PocomathInstance('Minimal Sqrt')
|
||||
ms.install(complexSqrt)
|
||||
await ms.importDependencies(['number', 'complex'])
|
||||
assert.deepStrictEqual(
|
||||
ms.sqrt(math.complex(0, -1)),
|
||||
math.complex(ms.negate(ms.sqrt(0.5)), ms.sqrt(0.5)))
|
||||
})
|
||||
|
||||
})
|
29
test/number/_all.mjs
Normal file
29
test/number/_all.mjs
Normal file
@ -0,0 +1,29 @@
|
||||
import assert from 'assert'
|
||||
import math from '../../src/pocomath.mjs'
|
||||
import PocomathInstance from '../../src/core/PocomathInstance.mjs'
|
||||
import * as numberSqrt from '../../src/number/sqrt.mjs'
|
||||
import * as complex from '../../src/complex/all.mjs'
|
||||
import * as numbers from '../../src/number/all.mjs'
|
||||
describe('number', () => {
|
||||
it('supports sqrt', () => {
|
||||
assert.strictEqual(math.sqrt(4), 2)
|
||||
assert.strictEqual(math.sqrt(NaN), NaN)
|
||||
assert.strictEqual(math.sqrt(2.25), 1.5)
|
||||
assert.deepStrictEqual(math.sqrt(-9), math.complex(0, 3))
|
||||
math.config.predictable = true
|
||||
assert.strictEqual(math.sqrt(-9), NaN)
|
||||
math.config.predictable = false
|
||||
assert.deepStrictEqual(math.sqrt(-0.25), math.complex(0, 0.5))
|
||||
})
|
||||
|
||||
it('supports sqrt by itself', () => {
|
||||
const no = new PocomathInstance('Numbers Only')
|
||||
no.install(numberSqrt)
|
||||
assert.strictEqual(no.sqrt(2.56), 1.6)
|
||||
assert.strictEqual(no.sqrt(-17), NaN)
|
||||
no.install(complex)
|
||||
no.install(numbers)
|
||||
assert.deepStrictEqual(no.sqrt(-16), no.complex(0,4))
|
||||
})
|
||||
|
||||
})
|
Loading…
Reference in New Issue
Block a user