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 * from './native.mjs'
|
||||||
export {complex} from './complex.mjs'
|
export * from '../generic/arithmetic.mjs'
|
||||||
export {add} from './add.mjs'
|
|
||||||
export {negate} from './negate.mjs'
|
|
||||||
export {subtract} from '../generic/subtract.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
|
* 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(['install', 'importDependencies'])
|
static reserved = new Set(['config', 'importDependencies', 'install', 'name'])
|
||||||
|
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.name = name
|
this.name = name
|
||||||
@ -17,6 +17,19 @@ export default class PocomathInstance {
|
|||||||
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: {}} // 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
|
* @param {string[]} types A list of type names
|
||||||
*/
|
*/
|
||||||
async importDependencies(types) {
|
async importDependencies(types) {
|
||||||
|
const typeSet = new Set(types)
|
||||||
|
typeSet.add('generic')
|
||||||
const doneSet = new Set(['self']) // nothing to do for self dependencies
|
const doneSet = new Set(['self']) // nothing to do for self dependencies
|
||||||
while (true) {
|
while (true) {
|
||||||
const requiredSet = new Set()
|
const requiredSet = new Set()
|
||||||
@ -96,7 +111,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
if (requiredSet.size === 0) break
|
if (requiredSet.size === 0) break
|
||||||
for (const name of requiredSet) {
|
for (const name of requiredSet) {
|
||||||
for (const type of types) {
|
for (const type of typeSet) {
|
||||||
try {
|
try {
|
||||||
const modName = `../${type}/${name}.mjs`
|
const modName = `../${type}/${name}.mjs`
|
||||||
const mod = await import(modName)
|
const mod = await import(modName)
|
||||||
@ -145,7 +160,8 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.Types[type] = spec
|
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.
|
* and if it has no implementations so far, set them up.
|
||||||
*/
|
*/
|
||||||
_invalidate(name) {
|
_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
|
const self = this
|
||||||
Object.defineProperty(this, name, {
|
Object.defineProperty(this, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => self._bundle(name)
|
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) {
|
if (name in this._affects) {
|
||||||
for (const ancestor of this._affects[name]) {
|
for (const ancestor of this._affects[name]) {
|
||||||
this._invalidate(ancestor)
|
this._invalidate(ancestor)
|
||||||
@ -229,29 +258,69 @@ export default class PocomathInstance {
|
|||||||
`Every implementation for ${name} uses an undefined type;\n`
|
`Every implementation for ${name} uses an undefined type;\n`
|
||||||
+ ` signatures: ${Object.keys(imps)}`)
|
+ ` signatures: ${Object.keys(imps)}`)
|
||||||
}
|
}
|
||||||
|
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const [signature, {uses, does}] of usableEntries) {
|
for (const [signature, {uses, does}] of usableEntries) {
|
||||||
if (uses.length === 0) {
|
if (uses.length === 0) {
|
||||||
tf_imps[signature] = does()
|
tf_imps[signature] = does()
|
||||||
} else {
|
} else {
|
||||||
const refs = {}
|
const refs = {}
|
||||||
let self_referential = false
|
let full_self_referential = false
|
||||||
|
let part_self_references = []
|
||||||
for (const dep of uses) {
|
for (const dep of uses) {
|
||||||
// TODO: handle signature-specific dependencies
|
const [func, needsig] = dep.split(/[()]/)
|
||||||
if (dep.includes('(')) {
|
if (func === 'self') {
|
||||||
throw new Error('signature specific reference unimplemented')
|
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)
|
||||||
}
|
}
|
||||||
if (dep === 'self') {
|
|
||||||
self_referential = true
|
|
||||||
} else {
|
} else {
|
||||||
refs[dep] = this[dep] // assume acyclic for now
|
if (part_self_references.length) {
|
||||||
|
throw new SyntaxError(
|
||||||
|
'typed-function does not support mixed full and '
|
||||||
|
+ 'partial self-reference')
|
||||||
|
}
|
||||||
|
full_self_referential = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 => {
|
tf_imps[signature] = this._typed.referToSelf(self => {
|
||||||
refs.self = self
|
refs.self = self
|
||||||
return does(refs)
|
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 {
|
} else {
|
||||||
tf_imps[signature] = does(refs)
|
tf_imps[signature] = does(refs)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
export default function dependencyExtractor(destinationSet) {
|
export default function dependencyExtractor(destinationSet) {
|
||||||
return new Proxy({}, {
|
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 {Types} from './Types/number.mjs'
|
||||||
export {add} from './add.mjs'
|
|
||||||
export {negate} from './negate.mjs'
|
export * from './native.mjs'
|
||||||
export {subtract} from '../generic/subtract.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 */
|
/* Core of pocomath: generates the default instance */
|
||||||
import PocomathInstance from './core/PocomathInstance.mjs'
|
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 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')
|
const math = new PocomathInstance('math')
|
||||||
math.install(numbers)
|
math.install(numbers)
|
||||||
math.install(bigints)
|
math.install(bigints)
|
||||||
math.install(complex)
|
math.install(complex)
|
||||||
|
math.install(generic)
|
||||||
|
|
||||||
export default math
|
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