feat: Template operations #41
@ -1,5 +1,3 @@
|
|||||||
export * from './Types/bigint.mjs'
|
export * from './Types/bigint.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {'bigint,bigint': () => (a,b) => a+b}
|
||||||
'...bigint': () => addends => addends.reduce((x,y) => x+y, 0n)
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import PocomathInstance from '../core/PocomathInstance.mjs'
|
import PocomathInstance from '../core/PocomathInstance.mjs'
|
||||||
import * as bigints from './native.mjs'
|
import * as bigints from './native.mjs'
|
||||||
import * as generic from '../generic/arithmetic.mjs'
|
import * as generic from '../generic/all.mjs'
|
||||||
|
|
||||||
export default PocomathInstance.merge('bigint', bigints, generic)
|
export default PocomathInstance.merge('bigint', bigints, generic)
|
||||||
|
5
src/bigint/compare.mjs
Normal file
5
src/bigint/compare.mjs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './Types/bigint.mjs'
|
||||||
|
|
||||||
|
export const compare = {
|
||||||
|
'bigint,bigint': () => (a,b) => a === b ? 0n : (a > b ? 1n : -1n)
|
||||||
|
}
|
@ -3,6 +3,7 @@ import gcdType from '../generic/gcdType.mjs'
|
|||||||
export * from './Types/bigint.mjs'
|
export * from './Types/bigint.mjs'
|
||||||
|
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
|
export {compare} from './compare.mjs'
|
||||||
export {divide} from './divide.mjs'
|
export {divide} from './divide.mjs'
|
||||||
export const gcd = gcdType('bigint')
|
export const gcd = gcdType('bigint')
|
||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
export * from './Types/Complex.mjs'
|
export * from './Types/Complex.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {
|
||||||
'...Complex': ({self}) => addends => {
|
/* Relying on conversions for both complex + number and complex + bigint
|
||||||
if (addends.length === 0) return {re:0, im:0}
|
* leads to an infinite loop when adding a number and a bigint, since they
|
||||||
const seed = addends.shift()
|
* both convert to Complex.
|
||||||
return addends.reduce(
|
*/
|
||||||
(w,z) => ({re: self(w.re, z.re), im: self(w.im, z.im)}), seed)
|
'Complex,number': ({
|
||||||
}
|
'self(number,number)': addNum,
|
||||||
|
'complex(any,any)': cplx
|
||||||
|
}) => (z,x) => cplx(addNum(z.re, x), z.im),
|
||||||
|
|
||||||
|
'Complex,bigint': ({
|
||||||
|
'self(bigint,bigint)': addBigInt,
|
||||||
|
'complex(any,any)': cplx
|
||||||
|
}) => (z,x) => cplx(addBigInt(z.re, x), z.im),
|
||||||
|
|
||||||
|
'Complex,Complex': ({
|
||||||
|
self,
|
||||||
|
'complex(any,any)': cplx
|
||||||
|
}) => (w,z) => cplx(self(w.re, z.re), self(w.im, z.im))
|
||||||
}
|
}
|
||||||
|
19
src/complex/equal.mjs
Normal file
19
src/complex/equal.mjs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export * from './Types/Complex.mjs'
|
||||||
|
|
||||||
|
export const equal = {
|
||||||
|
'Complex,number': ({
|
||||||
|
'isZero(number)': isZ,
|
||||||
|
'self(number,number)': eqNum
|
||||||
|
}) => (z, x) => eqNum(z.re, x) && isZ(z.im),
|
||||||
|
|
||||||
|
'Complex,bigint': ({
|
||||||
|
'isZero(bigint)': isZ,
|
||||||
|
'self(bigint,bigint)': eqBigInt
|
||||||
|
}) => (z, b) => eqBigInt(z.re, b) && isZ(z.im),
|
||||||
|
|
||||||
|
'Complex,Complex': ({self}) => (w,z) => self(w.re, z.re) && self(w.im, z.im),
|
||||||
|
|
||||||
|
'GaussianInteger,GaussianInteger': ({
|
||||||
|
'self(bigint,bigint)': eq
|
||||||
|
}) => (a,b) => eq(a.re, b.re) && eq(a.im, b.im)
|
||||||
|
}
|
@ -7,6 +7,7 @@ export {absquare} from './absquare.mjs'
|
|||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {conjugate} from './conjugate.mjs'
|
export {conjugate} from './conjugate.mjs'
|
||||||
export {complex} from './complex.mjs'
|
export {complex} from './complex.mjs'
|
||||||
|
export {equal} from './equal.mjs'
|
||||||
export {gcd} from './gcd.mjs'
|
export {gcd} from './gcd.mjs'
|
||||||
export {invert} from './invert.mjs'
|
export {invert} from './invert.mjs'
|
||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
|
@ -3,7 +3,7 @@ export * from './Types/Complex.mjs'
|
|||||||
export const sqrt = {
|
export const sqrt = {
|
||||||
Complex: ({
|
Complex: ({
|
||||||
config,
|
config,
|
||||||
zero,
|
isZero,
|
||||||
sign,
|
sign,
|
||||||
one,
|
one,
|
||||||
add,
|
add,
|
||||||
@ -16,11 +16,8 @@ export const sqrt = {
|
|||||||
}) => {
|
}) => {
|
||||||
if (config.predictable) {
|
if (config.predictable) {
|
||||||
return z => {
|
return z => {
|
||||||
const imZero = zero(z.im)
|
|
||||||
const imSign = sign(z.im)
|
|
||||||
const reOne = one(z.re)
|
const reOne = one(z.re)
|
||||||
const reSign = sign(z.re)
|
if (isZero(z.im) && sign(z.re) === reOne) return complex(self(z.re))
|
||||||
if (imSign === imZero && reSign === reOne) return complex(self(z.re))
|
|
||||||
const reTwo = add(reOne, reOne)
|
const reTwo = add(reOne, reOne)
|
||||||
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))),
|
||||||
@ -29,11 +26,8 @@ export const sqrt = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return z => {
|
return z => {
|
||||||
const imZero = zero(z.im)
|
|
||||||
const imSign = sign(z.im)
|
|
||||||
const reOne = one(z.re)
|
const reOne = one(z.re)
|
||||||
const reSign = sign(z.re)
|
if (isZero(z.im) && sign(z.re) === reOne) return self(z.re)
|
||||||
if (imSign === imZero && reSign === reOne) return self(z.re)
|
|
||||||
const reTwo = add(reOne, reOne)
|
const reTwo = add(reOne, reOne)
|
||||||
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))),
|
||||||
|
@ -5,6 +5,16 @@ import {subsetOfKeys, typesOfSignature} from './utils.mjs'
|
|||||||
|
|
||||||
const anySpec = {} // fixed dummy specification of 'any' type
|
const anySpec = {} // fixed dummy specification of 'any' type
|
||||||
|
|
||||||
|
const theTemplateParam = 'T' // First pass: only allow this one exact parameter
|
||||||
|
|
||||||
|
/* Returns a new signature just like sig but with the parameter replaced by
|
||||||
|
* the type
|
||||||
|
*/
|
||||||
|
function substituteInSig(sig, parameter, type) {
|
||||||
|
const pattern = new RegExp("\\b" + parameter + "\\b", 'g')
|
||||||
|
return sig.replaceAll(pattern, 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
|
||||||
@ -16,6 +26,7 @@ export default class PocomathInstance {
|
|||||||
'install',
|
'install',
|
||||||
'installType',
|
'installType',
|
||||||
'name',
|
'name',
|
||||||
|
'typeOf',
|
||||||
'Types',
|
'Types',
|
||||||
'undefinedTypes'
|
'undefinedTypes'
|
||||||
])
|
])
|
||||||
@ -26,11 +37,22 @@ 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: anySpec} // dummy entry to track the default 'any' type
|
/* List of types installed in the instance. We start with just dummies
|
||||||
|
* for the 'any' type and for type parameters:
|
||||||
|
*/
|
||||||
|
this.Types = {any: anySpec}
|
||||||
|
this.Types[theTemplateParam] = anySpec
|
||||||
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
||||||
|
/* The following gives for each type, a set of all types that could
|
||||||
|
* match in typed-function's dispatch algorithm before the given type.
|
||||||
|
* This is important because if we instantiate a template, we must
|
||||||
|
* instantiate it for all prior types as well, or else the wrong instance
|
||||||
|
* might match.
|
||||||
|
*/
|
||||||
|
this._priorTypes = {}
|
||||||
this._usedTypes = new Set() // all types that have occurred in a signature
|
this._usedTypes = new Set() // all types that have occurred in a signature
|
||||||
this._doomed = new Set() // for detecting circular reference
|
this._doomed = new Set() // for detecting circular reference
|
||||||
this._config = {predictable: false}
|
this._config = {predictable: false, epsilon: 1e-12}
|
||||||
const self = this
|
const self = this
|
||||||
this.config = new Proxy(this._config, {
|
this.config = new Proxy(this._config, {
|
||||||
get: (target, property) => target[property],
|
get: (target, property) => target[property],
|
||||||
@ -85,6 +107,20 @@ export default class PocomathInstance {
|
|||||||
* 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.
|
* operation in the body of the implementation.
|
||||||
|
*
|
||||||
|
* You can specify template implementations. If any item in the signature
|
||||||
|
* contains the word 'T' (currently the only allowed type parameter) then
|
||||||
|
* the signature/implementation is a template. The T can match any type
|
||||||
|
* of argument, and it may appear in the dependencies, where it is
|
||||||
|
* replaced by the matching type. A bare 'T' in the dependencies will be
|
||||||
|
* supplied with the name of the type as its value. See the implementation
|
||||||
|
* of `subtract` for an example.
|
||||||
|
* Usually templates are instantiated as needed, but for some heavily
|
||||||
|
* used functions, or functions with non-template signatures that refer
|
||||||
|
* to signatures generated from a template, it makes more sense to just
|
||||||
|
* instantiate the template immediately for all known types. This eager
|
||||||
|
* instantiation can be accomplished by prefixin the signature with an
|
||||||
|
* exclamation point.
|
||||||
*/
|
*/
|
||||||
install(ops) {
|
install(ops) {
|
||||||
if (ops instanceof PocomathInstance) {
|
if (ops instanceof PocomathInstance) {
|
||||||
@ -130,9 +166,16 @@ export default class PocomathInstance {
|
|||||||
|
|
||||||
_installInstance(other) {
|
_installInstance(other) {
|
||||||
for (const [type, spec] of Object.entries(other.Types)) {
|
for (const [type, spec] of Object.entries(other.Types)) {
|
||||||
|
if (type === 'any' || this._templateParam(type)) continue
|
||||||
this.installType(type, spec)
|
this.installType(type, spec)
|
||||||
}
|
}
|
||||||
this._installFunctions(other._imps)
|
const migrateImps = {}
|
||||||
|
for (const operator in other._imps) {
|
||||||
|
if (operator != 'typeOf') { // skip the builtin, we already have it
|
||||||
|
migrateImps[operator] = other._imps[operator]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._installFunctions(migrateImps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,7 +209,12 @@ export default class PocomathInstance {
|
|||||||
const mod = await import(modName)
|
const mod = await import(modName)
|
||||||
this.install(mod)
|
this.install(mod)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// No such module, but that's OK
|
if (!(err.message.includes('find'))) {
|
||||||
|
// Not just a error because module doesn't exist
|
||||||
|
// So take it seriously
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
// We don't care if a module doesn't exist, so merely proceed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doneSet.add(name)
|
doneSet.add(name)
|
||||||
@ -200,6 +248,10 @@ export default class PocomathInstance {
|
|||||||
* the corresponding changes to the _typed object immediately
|
* the corresponding changes to the _typed object immediately
|
||||||
*/
|
*/
|
||||||
installType(type, spec) {
|
installType(type, spec) {
|
||||||
|
if (this._templateParam(type)) {
|
||||||
|
throw new SyntaxError(
|
||||||
|
`Type name '${type}' reserved for template parameter`)
|
||||||
|
}
|
||||||
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}`)
|
||||||
@ -227,6 +279,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
||||||
this.Types[type] = spec
|
this.Types[type] = spec
|
||||||
|
this._priorTypes[type] = new Set()
|
||||||
/* Now add conversions to this type */
|
/* Now add conversions to this type */
|
||||||
for (const from in (spec.from || {})) {
|
for (const from in (spec.from || {})) {
|
||||||
if (from in this.Types) {
|
if (from in this.Types) {
|
||||||
@ -235,6 +288,8 @@ export default class PocomathInstance {
|
|||||||
while (nextSuper) {
|
while (nextSuper) {
|
||||||
this._typed.addConversion(
|
this._typed.addConversion(
|
||||||
{from, to: nextSuper, convert: spec.from[from]})
|
{from, to: nextSuper, convert: spec.from[from]})
|
||||||
|
this._invalidateDependents(':' + nextSuper)
|
||||||
|
this._priorTypes[nextSuper].add(from)
|
||||||
nextSuper = this.Types[nextSuper].refines
|
nextSuper = this.Types[nextSuper].refines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,23 +308,30 @@ export default class PocomathInstance {
|
|||||||
to: nextSuper,
|
to: nextSuper,
|
||||||
convert: this.Types[to].from[type]
|
convert: this.Types[to].from[type]
|
||||||
})
|
})
|
||||||
|
this._invalidateDependents(':' + nextSuper)
|
||||||
|
this._priorTypes[nextSuper].add(type)
|
||||||
nextSuper = this.Types[nextSuper].refines
|
nextSuper = this.Types[nextSuper].refines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (spec.refines) {
|
// Update all the subtype sets of supertypes up the chain, and
|
||||||
this._typed.addConversion(
|
// while we are at it add trivial conversions from subtypes to supertypes
|
||||||
{from: type, to: spec.refines, convert: x => x})
|
// to help typed-function match signatures properly:
|
||||||
}
|
|
||||||
this._subtypes[type] = new Set()
|
this._subtypes[type] = new Set()
|
||||||
// Update all the subtype sets of supertypes up the chain:
|
|
||||||
let nextSuper = spec.refines
|
let nextSuper = spec.refines
|
||||||
while (nextSuper) {
|
while (nextSuper) {
|
||||||
|
this._typed.addConversion(
|
||||||
|
{from: type, to: nextSuper, convert: x => x})
|
||||||
|
this._invalidateDependents(':' + nextSuper)
|
||||||
|
this._priorTypes[nextSuper].add(type)
|
||||||
this._subtypes[nextSuper].add(type)
|
this._subtypes[nextSuper].add(type)
|
||||||
nextSuper = this.Types[nextSuper].refines
|
nextSuper = this.Types[nextSuper].refines
|
||||||
}
|
}
|
||||||
// rebundle anything that uses the new type:
|
|
||||||
this._invalidateDependents(':' + type)
|
// update the typeOf function
|
||||||
|
const imp = {}
|
||||||
|
imp[type] = {uses: new Set(), does: () => () => type}
|
||||||
|
this._installFunctions({typeOf: imp})
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Returns a list of all types that have been mentioned in the
|
/* Returns a list of all types that have been mentioned in the
|
||||||
@ -292,13 +354,17 @@ export default class PocomathInstance {
|
|||||||
`Conflicting definitions of ${signature} for ${name}`)
|
`Conflicting definitions of ${signature} for ${name}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
opImps[signature] = behavior
|
// Must avoid aliasing into another instance:
|
||||||
|
opImps[signature] = {uses: behavior.uses, does: behavior.does}
|
||||||
for (const dep of behavior.uses) {
|
for (const dep of behavior.uses) {
|
||||||
const depname = dep.split('(', 1)[0]
|
const depname = dep.split('(', 1)[0]
|
||||||
if (depname === 'self') continue
|
if (depname === 'self' || this._templateParam(depname)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
this._addAffect(depname, name)
|
this._addAffect(depname, name)
|
||||||
}
|
}
|
||||||
for (const type of typesOfSignature(signature)) {
|
for (const type of typesOfSignature(signature)) {
|
||||||
|
if (this._templateParam(type)) continue
|
||||||
this._usedTypes.add(type)
|
this._usedTypes.add(type)
|
||||||
this._addAffect(':' + type, name)
|
this._addAffect(':' + type, name)
|
||||||
}
|
}
|
||||||
@ -307,6 +373,12 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* returns a boolean indicating whether t denotes a template parameter.
|
||||||
|
* We will start this out very simply: the special string `T` is always
|
||||||
|
* a template parameter, and that's the only one
|
||||||
|
*/
|
||||||
|
_templateParam(t) { return t === theTemplateParam }
|
||||||
|
|
||||||
_addAffect(dependency, dependent) {
|
_addAffect(dependency, dependent) {
|
||||||
if (dependency in this._affects) {
|
if (dependency in this._affects) {
|
||||||
this._affects[dependency].add(dependent)
|
this._affects[dependency].add(dependent)
|
||||||
@ -366,15 +438,121 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const [signature, {uses, does}] of usableEntries) {
|
for (const [rawSignature, behavior] of usableEntries) {
|
||||||
if (uses.length === 0) {
|
/* Check if it's an ordinary non-template signature */
|
||||||
tf_imps[signature] = does()
|
let explicit = true
|
||||||
|
for (const type of typesOfSignature(rawSignature)) {
|
||||||
|
if (this._templateParam(type)) { // template types need better check
|
||||||
|
explicit = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (explicit) {
|
||||||
|
this._addTFimplementation(tf_imps, rawSignature, behavior)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
/* It's a template, have to instantiate */
|
||||||
|
/* First, add the known instantiations, gathering all types needed */
|
||||||
|
if (!('instantiations' in behavior)) {
|
||||||
|
behavior.instantiations = new Set()
|
||||||
|
}
|
||||||
|
let instantiationSet = new Set()
|
||||||
|
let trimSignature = rawSignature
|
||||||
|
if (rawSignature.charAt(0) === '!') {
|
||||||
|
trimSignature = trimSignature.slice(1)
|
||||||
|
instantiationSet = this._usedTypes
|
||||||
} else {
|
} else {
|
||||||
|
for (const instType of behavior.instantiations) {
|
||||||
|
instantiationSet.add(instType)
|
||||||
|
for (const other of this._priorTypes[instType]) {
|
||||||
|
instantiationSet.add(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const instType of instantiationSet) {
|
||||||
|
if (this.Types[instType] === anySpec) continue
|
||||||
|
const signature =
|
||||||
|
substituteInSig(trimSignature, theTemplateParam, instType)
|
||||||
|
/* Don't override an explicit implementation: */
|
||||||
|
if (signature in imps) continue
|
||||||
|
const uses = new Set()
|
||||||
|
for (const dep of behavior.uses) {
|
||||||
|
if (this._templateParam(dep)) continue
|
||||||
|
uses.add(substituteInSig(dep, theTemplateParam, instType))
|
||||||
|
}
|
||||||
|
const patch = (refs) => {
|
||||||
|
const innerRefs = {}
|
||||||
|
for (const dep of behavior.uses) {
|
||||||
|
if (this._templateParam(dep)) {
|
||||||
|
innerRefs[dep] = instType
|
||||||
|
} else {
|
||||||
|
const outerName = substituteInSig(
|
||||||
|
dep, theTemplateParam, instType)
|
||||||
|
innerRefs[dep] = refs[outerName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const original = behavior.does(innerRefs)
|
||||||
|
return behavior.does(innerRefs)
|
||||||
|
}
|
||||||
|
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
||||||
|
}
|
||||||
|
/* Now add the catchall signature */
|
||||||
|
const signature = substituteInSig(
|
||||||
|
trimSignature, theTemplateParam, 'any')
|
||||||
|
/* The catchall signature has to detect the actual type of the call
|
||||||
|
* and add the new instantiations
|
||||||
|
*/
|
||||||
|
const argTypes = trimSignature.split(',')
|
||||||
|
let exemplar = -1
|
||||||
|
for (let i = 0; i < argTypes.length; ++i) {
|
||||||
|
const argType = argTypes[i].trim()
|
||||||
|
if (argType === theTemplateParam) {
|
||||||
|
exemplar = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exemplar < 0) {
|
||||||
|
throw new SyntaxError(
|
||||||
|
`Cannot find template parameter in ${rawSignature}`)
|
||||||
|
}
|
||||||
|
const self = this
|
||||||
|
const patch = (refs) => (...args) => {
|
||||||
|
const example = args[exemplar]
|
||||||
|
const instantiateFor = self.typeOf(example)
|
||||||
|
refs[theTemplateParam] = instantiateFor
|
||||||
|
behavior.instantiations.add(instantiateFor)
|
||||||
|
self._invalidate(name)
|
||||||
|
// And for now, we have to rely on the "any" implementation. Hope
|
||||||
|
// it matches the instantiated one!
|
||||||
|
return behavior.does(refs)(...args)
|
||||||
|
}
|
||||||
|
this._addTFimplementation(
|
||||||
|
tf_imps, signature, {uses: behavior.uses, does: patch})
|
||||||
|
}
|
||||||
|
const tf = this._typed(name, tf_imps)
|
||||||
|
Object.defineProperty(this, name, {configurable: true, value: tf})
|
||||||
|
return tf
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adapts Pocomath-style behavior specification (uses, does) for signature
|
||||||
|
* to typed-function implementations and inserts the result into plain object
|
||||||
|
* imps
|
||||||
|
*/
|
||||||
|
_addTFimplementation(imps, signature, behavior) {
|
||||||
|
const {uses, does} = behavior
|
||||||
|
if (uses.length === 0) {
|
||||||
|
imps[signature] = does()
|
||||||
|
return
|
||||||
|
}
|
||||||
const refs = {}
|
const refs = {}
|
||||||
let full_self_referential = false
|
let full_self_referential = false
|
||||||
let part_self_references = []
|
let part_self_references = []
|
||||||
for (const dep of uses) {
|
for (const dep of uses) {
|
||||||
const [func, needsig] = dep.split(/[()]/)
|
let [func, needsig] = dep.split(/[()]/)
|
||||||
|
const needTypes = needsig ? typesOfSignature(needsig) : new Set()
|
||||||
|
/* For now, punt on template parameters */
|
||||||
|
if (needTypes.has(theTemplateParam)) needsig = ''
|
||||||
if (func === 'self') {
|
if (func === 'self') {
|
||||||
if (needsig) {
|
if (needsig) {
|
||||||
if (full_self_referential) {
|
if (full_self_referential) {
|
||||||
@ -414,12 +592,14 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (full_self_referential) {
|
if (full_self_referential) {
|
||||||
tf_imps[signature] = this._typed.referToSelf(self => {
|
imps[signature] = this._typed.referToSelf(self => {
|
||||||
refs.self = self
|
refs.self = self
|
||||||
return does(refs)
|
return does(refs)
|
||||||
})
|
})
|
||||||
} else if (part_self_references.length) {
|
return
|
||||||
tf_imps[signature] = this._typed.referTo(
|
}
|
||||||
|
if (part_self_references.length) {
|
||||||
|
imps[signature] = this._typed.referTo(
|
||||||
...part_self_references, (...impls) => {
|
...part_self_references, (...impls) => {
|
||||||
for (let i = 0; i < part_self_references.length; ++i) {
|
for (let i = 0; i < part_self_references.length; ++i) {
|
||||||
refs[`self(${part_self_references[i]})`] = impls[i]
|
refs[`self(${part_self_references[i]})`] = impls[i]
|
||||||
@ -427,14 +607,8 @@ export default class PocomathInstance {
|
|||||||
return does(refs)
|
return does(refs)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
return
|
||||||
tf_imps[signature] = does(refs)
|
}
|
||||||
|
imps[signature] = does(refs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const tf = this._typed(name, tf_imps)
|
|
||||||
Object.defineProperty(this, name, {configurable: true, value: tf})
|
|
||||||
return tf
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './arithmetic.mjs'
|
export * from './arithmetic.mjs'
|
||||||
|
export * from './relational.mjs'
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import {reducingOperation} from './reducingOperation.mjs'
|
||||||
|
|
||||||
export * from './Types/generic.mjs'
|
export * from './Types/generic.mjs'
|
||||||
|
|
||||||
|
export const add = reducingOperation
|
||||||
export {lcm} from './lcm.mjs'
|
export {lcm} from './lcm.mjs'
|
||||||
export {mod} from './mod.mjs'
|
export {mod} from './mod.mjs'
|
||||||
export {multiply} from './multiply.mjs'
|
export const multiply = reducingOperation
|
||||||
export {divide} from './divide.mjs'
|
export {divide} from './divide.mjs'
|
||||||
export {sign} from './sign.mjs'
|
export {sign} from './sign.mjs'
|
||||||
export {sqrt} from './sqrt.mjs'
|
export {sqrt} from './sqrt.mjs'
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
export const divide = {
|
export const divide = {
|
||||||
'any,any': ({multiply, invert}) => (x, y) => multiply(x, invert(y))
|
'T,T': ({
|
||||||
|
'multiply(T,T)': multT,
|
||||||
|
'invert(T)': invT
|
||||||
|
}) => (x, y) => multT(x, invT(y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
/* Note we do not use a template here so that we can explicitly control
|
||||||
|
* which types this is instantiated for, namely the "integer" types, and
|
||||||
|
* not simply allow Pocomath to generate instances for any type it encounters.
|
||||||
|
*/
|
||||||
/* Returns a object that defines the gcd for the given type */
|
/* Returns a object that defines the gcd for the given type */
|
||||||
export default function(type) {
|
export default function(type) {
|
||||||
const producer = refs => {
|
const producer = refs => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export const lcm = {
|
export const lcm = {
|
||||||
'any,any': ({
|
'T,T': ({
|
||||||
multiply,
|
'multiply(T,T)': multT,
|
||||||
quotient,
|
'quotient(T,T)': quotT,
|
||||||
gcd}) => (a,b) => multiply(quotient(a, gcd(a,b)), b)
|
'gcd(T,T)': gcdT
|
||||||
|
}) => (a,b) => multT(quotT(a, gcdT(a,b)), b)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export const mod = {
|
export const mod = {
|
||||||
'any,any': ({
|
'T,T': ({
|
||||||
subtract,
|
'subtract(T,T)': subT,
|
||||||
multiply,
|
'multiply(T,T)': multT,
|
||||||
quotient}) => (a,m) => subtract(a, multiply(m, quotient(a,m)))
|
'quotient(T,T)': quotT
|
||||||
|
}) => (a,m) => subT(a, multT(m, quotT(a,m)))
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ export * from './Types/generic.mjs'
|
|||||||
export const multiply = {
|
export const multiply = {
|
||||||
'undefined': () => u => u,
|
'undefined': () => u => u,
|
||||||
'undefined,...any': () => (u, rest) => u,
|
'undefined,...any': () => (u, rest) => u,
|
||||||
|
any: () => x => x,
|
||||||
'any,undefined': () => (x, u) => u,
|
'any,undefined': () => (x, u) => u,
|
||||||
'any,any,...any': ({self}) => (a,b,rest) => {
|
'any,any,...any': ({self}) => (a,b,rest) => {
|
||||||
const later = [b, ...rest]
|
const later = [b, ...rest]
|
||||||
|
12
src/generic/reducingOperation.mjs
Normal file
12
src/generic/reducingOperation.mjs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export * from './Types/generic.mjs'
|
||||||
|
|
||||||
|
export const reducingOperation = {
|
||||||
|
'undefined': () => u => u,
|
||||||
|
'undefined,...any': () => (u, rest) => u,
|
||||||
|
'any,undefined': () => (x, u) => u,
|
||||||
|
any: () => x => x,
|
||||||
|
'any,any,...any': ({
|
||||||
|
self
|
||||||
|
}) => (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a)
|
||||||
|
}
|
||||||
|
|
54
src/generic/relational.mjs
Normal file
54
src/generic/relational.mjs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
export const compare = {
|
||||||
|
'undefined,undefined': () => () => 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isZero = {
|
||||||
|
'undefined': () => u => u === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export const equal = {
|
||||||
|
'!T,T': ({
|
||||||
|
'compare(T,T)': cmp,
|
||||||
|
'isZero(T)': isZ
|
||||||
|
}) => (x,y) => isZ(cmp(x,y))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unequal = {
|
||||||
|
'T,T': ({'equal(T.T)': eq}) => (x,y) => !(eq(x,y))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const larger = {
|
||||||
|
'T,T': ({
|
||||||
|
'compare(T,T)': cmp,
|
||||||
|
'one(T)' : uno
|
||||||
|
}) => (x,y) => cmp(x,y) === uno(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const largerEq = {
|
||||||
|
'T,T': ({
|
||||||
|
'compare(T,T)': cmp,
|
||||||
|
'one(T)' : uno,
|
||||||
|
'isZero(T)' : isZ
|
||||||
|
}) => (x,y) => {
|
||||||
|
const c = cmp(x,y)
|
||||||
|
return isZ(c) || c === uno(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const smaller = {
|
||||||
|
'T,T': ({
|
||||||
|
'compare(T,T)': cmp,
|
||||||
|
'one(T)' : uno,
|
||||||
|
'isZero(T)' : isZ
|
||||||
|
}) => (x,y) => {
|
||||||
|
const c = cmp(x,y)
|
||||||
|
return !isZ(c) && c !== uno(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const smallerEq = {
|
||||||
|
'T,T': ({
|
||||||
|
'compare(T,T)': cmp,
|
||||||
|
'one(T)' : uno
|
||||||
|
}) => (x,y) => cmp(x,y) !== uno(y)
|
||||||
|
}
|
@ -1,6 +1,3 @@
|
|||||||
export const sign = {
|
export const sign = {
|
||||||
any: ({negate, divide, abs}) => x => {
|
T: ({'compare(T,T)': cmp, 'zero(T)': Z}) => x => cmp(x, Z(x))
|
||||||
if (x === negate(x)) return x // zero
|
|
||||||
return divide(x, abs(x))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const square = {
|
export const square = {
|
||||||
any: ({multiply}) => x => multiply(x,x)
|
T: ({'multiply(T,T)': multT}) => x => multT(x,x)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const subtract = {
|
export const subtract = {
|
||||||
'any,any': ({add, negate}) => (x,y) => add(x, negate(y))
|
'T,T': ({'add(T,T)': addT, 'negate(T)': negT}) => (x,y) => addT(x, negT(y))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
export * from './Types/number.mjs'
|
export * from './Types/number.mjs'
|
||||||
|
|
||||||
export const add = {
|
export const add = {'number,number': () => (m,n) => m+n}
|
||||||
'...number': () => addends => addends.reduce((x,y) => x+y, 0),
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import PocomathInstance from '../core/PocomathInstance.mjs'
|
import PocomathInstance from '../core/PocomathInstance.mjs'
|
||||||
import * as numbers from './native.mjs'
|
import * as numbers from './native.mjs'
|
||||||
import * as generic from '../generic/arithmetic.mjs'
|
import * as generic from '../generic/all.mjs'
|
||||||
|
|
||||||
export default PocomathInstance.merge('number', numbers, generic)
|
export default PocomathInstance.merge('number', numbers, generic)
|
||||||
|
|
||||||
|
52
src/number/compare.mjs
Normal file
52
src/number/compare.mjs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* Lifted from mathjs/src/utils/number.js */
|
||||||
|
/**
|
||||||
|
* Minimum number added to one that makes the result different than one
|
||||||
|
*/
|
||||||
|
export const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two floating point numbers.
|
||||||
|
* @param {number} x First value to compare
|
||||||
|
* @param {number} y Second value to compare
|
||||||
|
* @param {number} [epsilon] The maximum relative difference between x and y
|
||||||
|
* If epsilon is undefined or null, the function will
|
||||||
|
* test whether x and y are exactly equal.
|
||||||
|
* @return {boolean} whether the two numbers are nearly equal
|
||||||
|
*/
|
||||||
|
function nearlyEqual (x, y, epsilon) {
|
||||||
|
// if epsilon is null or undefined, test whether x and y are exactly equal
|
||||||
|
if (epsilon === null || epsilon === undefined) {
|
||||||
|
return x === y
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x === y) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NaN
|
||||||
|
if (isNaN(x) || isNaN(y)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point x and y should be finite
|
||||||
|
if (isFinite(x) && isFinite(y)) {
|
||||||
|
// check numbers are very close, needed when comparing numbers near zero
|
||||||
|
const diff = Math.abs(x - y)
|
||||||
|
if (diff < DBL_EPSILON) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// use relative error
|
||||||
|
return diff <= Math.max(Math.abs(x), Math.abs(y)) * epsilon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infinite and Number or negative Infinite and positive Infinite cases
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
/* End of copied section */
|
||||||
|
|
||||||
|
export const compare = {
|
||||||
|
'number,number': ({
|
||||||
|
config
|
||||||
|
}) => (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1)
|
||||||
|
}
|
@ -4,6 +4,7 @@ export * from './Types/number.mjs'
|
|||||||
|
|
||||||
export {abs} from './abs.mjs'
|
export {abs} from './abs.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
|
export {compare} from './compare.mjs'
|
||||||
export const gcd = gcdType('NumInt')
|
export const gcd = gcdType('NumInt')
|
||||||
export {invert} from './invert.mjs'
|
export {invert} from './invert.mjs'
|
||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
|
import PocomathInstance from '../src/core/PocomathInstance.mjs'
|
||||||
import math from '../src/pocomath.mjs'
|
import math from '../src/pocomath.mjs'
|
||||||
|
|
||||||
describe('The default full pocomath instance "math"', () => {
|
describe('The default full pocomath instance "math"', () => {
|
||||||
@ -10,14 +11,25 @@ describe('The default full pocomath instance "math"', () => {
|
|||||||
assert.strictEqual(undef.length, 0)
|
assert.strictEqual(undef.length, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has a built-in typeOf operator', () => {
|
||||||
|
assert.strictEqual(math.typeOf(42), 'NumInt')
|
||||||
|
assert.strictEqual(math.typeOf(-1.5), 'number')
|
||||||
|
assert.strictEqual(math.typeOf(-42n), 'bigint')
|
||||||
|
assert.strictEqual(math.typeOf(undefined), 'undefined')
|
||||||
|
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger')
|
||||||
|
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex')
|
||||||
|
})
|
||||||
|
|
||||||
it('can subtract numbers', () => {
|
it('can subtract numbers', () => {
|
||||||
assert.strictEqual(math.subtract(12, 5), 7)
|
assert.strictEqual(math.subtract(12, 5), 7)
|
||||||
|
//assert.strictEqual(math.subtract(3n, 1.5), 1.5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can add numbers', () => {
|
it('can add numbers', () => {
|
||||||
assert.strictEqual(math.add(3, 4), 7)
|
assert.strictEqual(math.add(3, 4), 7)
|
||||||
assert.strictEqual(math.add(1.5, 2.5, 3.5), 7.5)
|
assert.strictEqual(math.add(1.5, 2.5, 3.5), 7.5)
|
||||||
assert.strictEqual(math.add(Infinity), Infinity)
|
assert.strictEqual(math.add(Infinity), Infinity)
|
||||||
|
assert.throws(() => math.add(3n, -1.5), TypeError)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can negate numbers', () => {
|
it('can negate numbers', () => {
|
||||||
@ -26,16 +38,17 @@ describe('The default full pocomath instance "math"', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can be extended', () => {
|
it('can be extended', () => {
|
||||||
math.installType('stringK', {
|
const stretch = PocomathInstance.merge(math) // clone to not pollute math
|
||||||
|
stretch.installType('stringK', {
|
||||||
test: s => typeof s === 'string' && s.charAt(0) === 'K',
|
test: s => typeof s === 'string' && s.charAt(0) === 'K',
|
||||||
before: ['string']
|
before: ['string']
|
||||||
})
|
})
|
||||||
math.install({
|
stretch.install({
|
||||||
add: {
|
add: {
|
||||||
'...stringK': () => addends => addends.reduce((x,y) => x+y, '')
|
'...stringK': () => addends => addends.reduce((x,y) => x+y, '')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.strictEqual(math.add('Kilroy','K is here'), 'KilroyK is here')
|
assert.strictEqual(stretch.add('Kilroy','K is here'), 'KilroyK is here')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles complex numbers', () => {
|
it('handles complex numbers', () => {
|
||||||
@ -46,10 +59,10 @@ describe('The default full pocomath instance "math"', () => {
|
|||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
math.subtract(math.complex(1,1), math.complex(2,-2)),
|
math.subtract(math.complex(1,1), math.complex(2,-2)),
|
||||||
math.complex(-1,3))
|
math.complex(-1,3))
|
||||||
|
assert.strictEqual(math.negate(math.complex(3, 8)).im, -8)
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
math.subtract(16, math.add(3, math.complex(0,4), 2)),
|
math.subtract(16, math.add(3, math.complex(0,4), 2)),
|
||||||
math.complex(11, -4))
|
math.complex(11, -4))
|
||||||
assert.strictEqual(math.negate(math.complex(3, 8)).im, -8)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles bigints', () => {
|
it('handles bigints', () => {
|
||||||
|
@ -38,6 +38,13 @@ describe('complex', () => {
|
|||||||
math.complex(ms.negate(ms.sqrt(0.5)), ms.sqrt(0.5)))
|
math.complex(ms.negate(ms.sqrt(0.5)), ms.sqrt(0.5)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('checks for equality', () => {
|
||||||
|
assert.ok(math.equal(math.complex(3,0), 3))
|
||||||
|
assert.ok(math.equal(math.complex(3,2), math.complex(3, 2)))
|
||||||
|
assert.ok(!(math.equal(math.complex(45n, 3n), math.complex(45n, -3n))))
|
||||||
|
assert.ok(!(math.equal(math.complex(45n, 3n), 45n)))
|
||||||
|
})
|
||||||
|
|
||||||
it('computes gcd', () => {
|
it('computes gcd', () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
|
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
|
||||||
|
@ -3,6 +3,7 @@ import math from '../src/pocomath.mjs'
|
|||||||
import PocomathInstance from '../src/core/PocomathInstance.mjs'
|
import PocomathInstance from '../src/core/PocomathInstance.mjs'
|
||||||
import * as numbers from '../src/number/all.mjs'
|
import * as numbers from '../src/number/all.mjs'
|
||||||
import * as numberAdd from '../src/number/add.mjs'
|
import * as numberAdd from '../src/number/add.mjs'
|
||||||
|
import {add as genericAdd} from '../src/generic/arithmetic.mjs'
|
||||||
import * as complex from '../src/complex/all.mjs'
|
import * as complex from '../src/complex/all.mjs'
|
||||||
import * as complexAdd from '../src/complex/add.mjs'
|
import * as complexAdd from '../src/complex/add.mjs'
|
||||||
import * as complexNegate from '../src/complex/negate.mjs'
|
import * as complexNegate from '../src/complex/negate.mjs'
|
||||||
@ -66,6 +67,7 @@ describe('A custom instance', () => {
|
|||||||
const cherry = new PocomathInstance('cherry')
|
const cherry = new PocomathInstance('cherry')
|
||||||
cherry.install(numberAdd)
|
cherry.install(numberAdd)
|
||||||
await extendToComplex(cherry)
|
await extendToComplex(cherry)
|
||||||
|
cherry.install({add: genericAdd})
|
||||||
/* Now we have an instance that supports addition for number and complex
|
/* Now we have an instance that supports addition for number and complex
|
||||||
and little else:
|
and little else:
|
||||||
*/
|
*/
|
||||||
|
@ -30,4 +30,11 @@ describe('number', () => {
|
|||||||
it('computes gcd', () => {
|
it('computes gcd', () => {
|
||||||
assert.strictEqual(math.gcd(15, 35), 5)
|
assert.strictEqual(math.gcd(15, 35), 5)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('compares numbers', () => {
|
||||||
|
assert.ok(math.smaller(12,13.5))
|
||||||
|
assert.ok(math.equal(Infinity, Infinity))
|
||||||
|
assert.ok(math.largerEq(12.5, math.divide(25,2)))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user