From 1444b9828ffb1c5663ee87856150a9c0b3bd5956 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 6 Aug 2022 08:27:44 -0700 Subject: [PATCH] refactor(Complex): Now a template type! This means that the real and imaginary parts of a Complex must now be the same type. This seems like a real benefit: a Complex with a number real part and a bigint imaginary part does not seem sensible. Note that this is now straining typed-function in (at least) the following ways: (1) In this change, it was necessary to remove the logic that the square root of a negative number calls complex square root, which then calls back to the number square root in its algorithm. (This was creating a circular reference in the typed-function which the old implementation of Complex was somehow sidestepping.) (2) typed-function could not follow conversions that would be allowed by uninstantiated templates (e.g. number => Complex if the latter template has not been instantiated) and so the facility for instantiating a template was surfaced (and for example is called explicitly in the demo loader `extendToComplex`. Similarly, this necessitated making the unary signature of the `complex` conversion function explicit, rather than just via implicit conversion to Complex. (3) I find the order of implementations is mattering more in typed-function definitions, implying that typed-function's sorting algorithm is having trouble distinguishing alternatives. But otherwise, the conversion went quite smoothly and I think is a good demo of the power of this approach. And I expect that it will work even more smoothly if some of the underlying facilities (subtypes, template types) are integrated into typed-function. --- src/bigint/sqrt.mjs | 22 ++++++++---- src/complex/Types/Complex.mjs | 31 ++++++++-------- src/complex/abs.mjs | 5 ++- src/complex/absquare.mjs | 5 ++- src/complex/add.mjs | 22 +++--------- src/complex/associate.mjs | 16 ++++----- src/complex/complex.mjs | 6 +++- src/complex/conjugate.mjs | 5 ++- src/complex/equalTT.mjs | 33 ++++++++++------- src/complex/extendToComplex.mjs | 13 +++++++ src/complex/gcd.mjs | 16 +++++---- src/complex/invert.mjs | 13 ++++--- src/complex/isZero.mjs | 2 +- src/complex/multiply.mjs | 15 ++++---- src/complex/quotient.mjs | 4 ++- src/complex/roundquotient.mjs | 20 +++++------ src/complex/sqrt.mjs | 49 ++++++++++++------------- src/core/PocomathInstance.mjs | 59 ++++++++++++++++++++++--------- src/generic/reducingOperation.mjs | 1 + src/number/sqrt.mjs | 9 +++-- src/ops/floor.mjs | 4 +-- src/tuple/Types/Tuple.mjs | 7 +++- test/_pocomath.mjs | 12 +++---- test/complex/_all.mjs | 9 +++++ test/custom.mjs | 19 ++++++---- 25 files changed, 240 insertions(+), 157 deletions(-) diff --git a/src/bigint/sqrt.mjs b/src/bigint/sqrt.mjs index ad6bd67..4d34513 100644 --- a/src/bigint/sqrt.mjs +++ b/src/bigint/sqrt.mjs @@ -2,14 +2,18 @@ export * from './Types/bigint.mjs' import isqrt from 'bigint-isqrt' export const sqrt = { - bigint: ({config, complex, 'self(Complex)': complexSqrt}) => { + bigint: ({ + config, + 'complex(bigint,bigint)': cplx, + 'negate(bigint)': neg + }) => { if (config.predictable) { // Don't just return the constant isqrt here because the object // gets decorated with info that might need to be different // for different PocomathInstancss return b => isqrt(b) } - if (!complexSqrt) { + if (!cplx) { return b => { if (b >= 0n) { const trial = isqrt(b) @@ -19,12 +23,16 @@ export const sqrt = { } } return b => { - if (b >= 0n) { - const trial = isqrt(b) - if (trial * trial === b) return trial - return undefined + if (b === undefined) return undefined + let real = true + if (b < 0n) { + b = neg(b) + real = false } - return complexSqrt(complex(b)) + const trial = isqrt(b) + if (trial * trial !== b) return undefined + if (real) return trial + return cplx(0n, trial) } } } diff --git a/src/complex/Types/Complex.mjs b/src/complex/Types/Complex.mjs index e7644b9..0fa4107 100644 --- a/src/complex/Types/Complex.mjs +++ b/src/complex/Types/Complex.mjs @@ -1,30 +1,27 @@ import PocomathInstance from '../../core/PocomathInstance.mjs' -/* 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 - * insist on some numeric or scalar supertype). - */ -function isComplex(z) { - return z && typeof z === 'object' && 're' in z && 'im' in z -} - const Complex = new PocomathInstance('Complex') +// Base type that should generally not be used directly Complex.installType('Complex', { - test: isComplex, - from: { - number: x => ({re: x, im: 0}) - } + test: z => z && typeof z === 'object' && 're' in z && 'im' in z }) -Complex.installType('GaussianInteger', { - test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint', - refines: 'Complex', +// Now the template type: Complex numbers are actually always homeogeneous +// in their component types. +Complex.installType('Complex', { + infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]), + test: testT => z => testT(z.re) && testT(z.im), from: { - bigint: x => ({re: x, im: 0n}) + T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T) + U: convert => u => { + const t = convert(u) + return ({re: t, im: t-t}) + }, + 'Complex': convert => cu => ({re: convert(cu.re), im: convert(cu.im)}) } }) Complex.promoteUnary = { - Complex: ({self,complex}) => z => complex(self(z.re), self(z.im)) + 'Complex': ({'self(T)': me, complex}) => z => complex(me(z.re), me(z.im)) } export {Complex} diff --git a/src/complex/abs.mjs b/src/complex/abs.mjs index 48d5a7b..47fc88d 100644 --- a/src/complex/abs.mjs +++ b/src/complex/abs.mjs @@ -1,5 +1,8 @@ export * from './Types/Complex.mjs' export const abs = { - Complex: ({sqrt, 'absquare(Complex)': absq}) => z => sqrt(absq(z)) + 'Complex': ({ + 'sqrt(T)': sqt, + 'absquare(Complex)': absq + }) => z => sqt(absq(z)) } diff --git a/src/complex/absquare.mjs b/src/complex/absquare.mjs index cfa1f30..913124b 100644 --- a/src/complex/absquare.mjs +++ b/src/complex/absquare.mjs @@ -1,5 +1,8 @@ export * from './Types/Complex.mjs' export const absquare = { - Complex: ({add, square}) => z => add(square(z.re), square(z.im)) + 'Complex': ({ + 'add(T,T)': plus, + 'square(T)': sqr + }) => z => plus(sqr(z.re), sqr(z.im)) } diff --git a/src/complex/add.mjs b/src/complex/add.mjs index 0c178d6..9afdd90 100644 --- a/src/complex/add.mjs +++ b/src/complex/add.mjs @@ -1,22 +1,8 @@ export * from './Types/Complex.mjs' export const add = { - /* Relying on conversions for both complex + number and complex + bigint - * leads to an infinite loop when adding a number and a bigint, since they - * both convert to Complex. - */ - 'Complex,number': ({ - 'self(number,number)': addNum, - 'complex(number,number)': cplx - }) => (z,x) => cplx(addNum(z.re, x), z.im), - - 'Complex,bigint': ({ - 'self(bigint,bigint)': addBigInt, - 'complex(bigint,bigint)': cplx - }) => (z,x) => cplx(addBigInt(z.re, x), z.im), - - 'Complex,Complex': ({ - self, - complex - }) => (w,z) => complex(self(w.re, z.re), self(w.im, z.im)) + 'Complex,Complex': ({ + 'self(T,T)': me, + 'complex(T,T)': cplx + }) => (w,z) => cplx(me(w.re, z.re), me(w.im, z.im)) } diff --git a/src/complex/associate.mjs b/src/complex/associate.mjs index f3106b4..10c799c 100644 --- a/src/complex/associate.mjs +++ b/src/complex/associate.mjs @@ -2,16 +2,16 @@ export * from './Types/Complex.mjs' /* Returns true if w is z multiplied by a complex unit */ export const associate = { - 'Complex,Complex': ({ - 'multiply(Complex,Complex)': times, - 'equalTT(Complex,Complex)': eq, - zero, - one, - complex, - 'negate(Complex)': neg + 'Complex,Complex': ({ + 'multiply(Complex,Complex)': times, + 'equalTT(Complex,Complex)': eq, + 'zero(T)': zr, + 'one(T)': uno, + 'complex(T,T)': cplx, + 'negate(Complex)': neg }) => (w,z) => { if (eq(w,z) || eq(w,neg(z))) return true - const ti = times(z, complex(zero(z.re), one(z.im))) + const ti = times(z, cplx(zr(z.re), uno(z.im))) return eq(w,ti) || eq(w,neg(ti)) } } diff --git a/src/complex/complex.mjs b/src/complex/complex.mjs index c34434c..a5a24f5 100644 --- a/src/complex/complex.mjs +++ b/src/complex/complex.mjs @@ -12,5 +12,9 @@ export const complex = { 'undefined,undefined': () => (u, v) => u, 'T,T': () => (x, y) => ({re: x, im: y}), /* Take advantage of conversions in typed-function */ - Complex: () => z => z + // 'Complex': () => z => z + /* But help out because without templates built in to typed-function, + * type inference turns out to be too hard + */ + 'T': ({'zero(T)': zr}) => x => ({re: x, im: zr(x)}) } diff --git a/src/complex/conjugate.mjs b/src/complex/conjugate.mjs index 3139506..b94495d 100644 --- a/src/complex/conjugate.mjs +++ b/src/complex/conjugate.mjs @@ -1,6 +1,9 @@ export * from './Types/Complex.mjs' export const conjugate = { - Complex: ({negate, complex}) => z => complex(z.re, negate(z.im)) + 'Complex': ({ + 'negate(T)': neg, + 'complex(T,T)': cplx + }) => z => cplx(z.re, neg(z.im)) } diff --git a/src/complex/equalTT.mjs b/src/complex/equalTT.mjs index 43fe0d1..6899aa0 100644 --- a/src/complex/equalTT.mjs +++ b/src/complex/equalTT.mjs @@ -1,19 +1,26 @@ export * from './Types/Complex.mjs' export const equalTT = { - 'Complex,number': ({ - 'isZero(number)': isZ, - 'self(number,number)': eqNum - }) => (z, x) => eqNum(z.re, x) && isZ(z.im), + 'Complex,Complex': ({ + 'self(T,T)': me + }) => (w,z) => me(w.re, z.re) && me(w.im, z.im), + // NOTE: Although I do not understand exactly why, with typed-function@3.0's + // matching algorithm, the above template must come first to ensure the + // most specific match to a template call. I.e, if one of the below + // comes first, a call with two complex numbers can match via conversions + // with (Complex>, Complex) (!, hopefully in some + // future iteration typed-function will be smart enough to prefer + // Complex, Complex. Possibly the problem is in Pocomath's bolted-on + // type resolution and the difficulty will go away when features are moved into + // typed-function. + 'Complex,T': ({ + 'isZero(T)': isZ, + 'self(T,T)': eqReal + }) => (z, x) => eqReal(z.re, x) && isZ(z.im), - 'Complex,bigint': ({ - 'isZero(bigint)': isZ, - 'self(bigint,bigint)': eqBigInt - }) => (z, b) => eqBigInt(z.re, b) && isZ(z.im), + 'T,Complex': ({ + 'isZero(T)': isZ, + 'self(T,T)': eqReal + }) => (b, z) => eqReal(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) } diff --git a/src/complex/extendToComplex.mjs b/src/complex/extendToComplex.mjs index 21e1a29..62e2e88 100644 --- a/src/complex/extendToComplex.mjs +++ b/src/complex/extendToComplex.mjs @@ -15,4 +15,17 @@ export default async function extendToComplex(pmath) { // Guess it wasn't a method available in complex; no worries } } + // Since extension to complex was specifically requested, instantiate + // all of the templates so that the associated type conversions will + // be available to make function calls work immediately: + for (const baseType in pmath.Types) { + if (baseType in pmath.Templates || baseType.includes('<')) { + continue // don't mess with templates + } + const ignore = new Set(['undefined', 'any', 'T', 'ground']) + if (ignore.has(baseType)) continue + // (What we really want is a check for "numeric" types but we don't + // have that concept (yet?)). If we did, we'd instantiate just for those... + pmath.instantiateTemplate('Complex', baseType) + } } diff --git a/src/complex/gcd.mjs b/src/complex/gcd.mjs index 238d811..30b7dad 100644 --- a/src/complex/gcd.mjs +++ b/src/complex/gcd.mjs @@ -2,16 +2,20 @@ import PocomathInstance from '../core/PocomathInstance.mjs' import * as Complex from './Types/Complex.mjs' import gcdType from '../generic/gcdType.mjs' +const gcdComplexRaw = {} +Object.assign(gcdComplexRaw, gcdType('Complex')) +Object.assign(gcdComplexRaw, gcdType('Complex')) const imps = { - gcdGIRaw: gcdType('GaussianInteger'), + gcdComplexRaw, gcd: { // Only return gcds with positive real part - 'GaussianInteger,GaussianInteger': ({ - 'gcdGIRaw(GaussianInteger,GaussianInteger)': gcdRaw, - 'sign(bigint)': sgn, - 'negate(GaussianInteger)': neg + 'Complex,Complex': ({ + 'gcdComplexRaw(Complex,Complex)': gcdRaw, + 'sign(T)': sgn, + 'one(T)': uno, + 'negate(Complex)': neg }) => (z,m) => { const raw = gcdRaw(z, m) - if (sgn(raw.re) === 1n) return raw + if (sgn(raw.re) === uno(raw.re)) return raw return neg(raw) } } diff --git a/src/complex/invert.mjs b/src/complex/invert.mjs index dc7779a..2f68e43 100644 --- a/src/complex/invert.mjs +++ b/src/complex/invert.mjs @@ -1,9 +1,14 @@ export * from './Types/Complex.mjs' export const invert = { - Complex: ({conjugate, absquare, complex, divide}) => z => { - const c = conjugate(z) - const d = absquare(z) - return complex(divide(c.re, d), divide(c.im, d)) + 'Complex': ({ + 'conjugate(Complex)': conj, + 'absquare(Complex)': asq, + 'complex(T,T)': cplx, + 'divide(T,T)': div + }) => z => { + const c = conj(z) + const d = asq(z) + return cplx(div(c.re, d), div(c.im, d)) } } diff --git a/src/complex/isZero.mjs b/src/complex/isZero.mjs index 1eaad7f..01a2f51 100644 --- a/src/complex/isZero.mjs +++ b/src/complex/isZero.mjs @@ -1,5 +1,5 @@ export * from './Types/Complex.mjs' export const isZero = { - Complex: ({self}) => z => self(z.re) && self(z.im) + 'Complex': ({'self(T)': me}) => z => me(z.re) && me(z.im) } diff --git a/src/complex/multiply.mjs b/src/complex/multiply.mjs index e1b46fe..e059a91 100644 --- a/src/complex/multiply.mjs +++ b/src/complex/multiply.mjs @@ -1,14 +1,15 @@ export * from './Types/Complex.mjs' export const multiply = { - 'Complex,Complex': ({ - 'complex(any,any)': cplx, - add, - subtract, - self + 'Complex,Complex': ({ + 'complex(T,T)': cplx, + 'add(T,T)': plus, + 'subtract(T,T)': sub, + 'self(T,T)': me, + 'conjugate(T)': conj // makes quaternion multiplication work }) => (w,z) => { return cplx( - subtract(self(w.re, z.re), self(w.im, z.im)), - add(self(w.re, z.im), self(w.im, z.re))) + sub(me(w.re, z.re), me(conj(w.im), z.im)), + plus(me(conj(w.re), z.im), me(w.im, z.re))) } } diff --git a/src/complex/quotient.mjs b/src/complex/quotient.mjs index 20090e0..32299ca 100644 --- a/src/complex/quotient.mjs +++ b/src/complex/quotient.mjs @@ -1,5 +1,7 @@ export * from './roundquotient.mjs' export const quotient = { - 'Complex,Complex': ({roundquotient}) => (w,z) => roundquotient(w,z) + 'Complex,Complex': ({ + 'roundquotient(Complex,Complex)': rq + }) => (w,z) => rq(w,z) } diff --git a/src/complex/roundquotient.mjs b/src/complex/roundquotient.mjs index c9b2cbc..5c25765 100644 --- a/src/complex/roundquotient.mjs +++ b/src/complex/roundquotient.mjs @@ -1,17 +1,17 @@ export * from './Types/Complex.mjs' export const roundquotient = { - 'Complex,Complex': ({ - 'isZero(Complex)': isZ, - conjugate, - 'multiply(Complex,Complex)': mult, - absquare, - self, - complex + 'Complex,Complex': ({ + 'isZero(Complex)': isZ, + 'conjugate(Complex)': conj, + 'multiply(Complex,Complex)': mult, + 'absquare(Complex)': asq, + 'self(T,T)': me, + 'complex(T,T)': cplx }) => (n,d) => { if (isZ(d)) return d - const cnum = mult(n, conjugate(d)) - const dreal = absquare(d) - return complex(self(cnum.re, dreal), self(cnum.im, dreal)) + const cnum = mult(n, conj(d)) + const dreal = asq(d) + return cplx(me(cnum.re, dreal), me(cnum.im, dreal)) } } diff --git a/src/complex/sqrt.mjs b/src/complex/sqrt.mjs index 3557c6d..d60ed6a 100644 --- a/src/complex/sqrt.mjs +++ b/src/complex/sqrt.mjs @@ -1,38 +1,39 @@ export * from './Types/Complex.mjs' export const sqrt = { - Complex: ({ + 'Complex': ({ config, - isZero, - sign, - one, - add, - complex, - multiply, - self, - divide, - 'abs(Complex)': abs, - subtract + 'isZero(T)': isZ, + 'sign(T)': sgn, + 'one(T)': uno, + 'add(T,T)': plus, + 'complex(T)': cplxU, + 'complex(T,T)': cplxB, + 'multiply(T,T)': mult, + 'self(T)': me, + 'divide(T,T)': div, + 'abs(Complex)': absC, + 'subtract(T,T)': sub }) => { if (config.predictable) { return z => { - const reOne = one(z.re) - if (isZero(z.im) && sign(z.re) === reOne) return complex(self(z.re)) - const reTwo = add(reOne, reOne) - return complex( - multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))), - self(divide(subtract(abs(z),z.re), reTwo)) + const reOne = uno(z.re) + if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re)) + const reTwo = plus(reOne, reOne) + return cplxB( + mult(sgn(z.im), me(div(plus(absC(z),z.re), reTwo))), + me(div(sub(absC(z),z.re), reTwo)) ) } } return z => { - const reOne = one(z.re) - if (isZero(z.im) && sign(z.re) === reOne) return self(z.re) - const reTwo = add(reOne, reOne) - return complex( - multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))), - self(divide(subtract(abs(z),z.re), reTwo)) - ) + const reOne = uno(z.re) + if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re) + const reTwo = plus(reOne, reOne) + const reSqrt = me(div(plus(absC(z),z.re), reTwo)) + const imSqrt = me(div(sub(absC(z),z.re), reTwo)) + if (reSqrt === undefined || imSqrt === undefined) return undefined + return cplxB(mult(sgn(z.im), reSqrt), imSqrt) } } } diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 05636d4..b01c41f 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -28,6 +28,7 @@ export default class PocomathInstance { 'importDependencies', 'install', 'installType', + 'instantiateTemplate', 'joinTypes', 'name', 'self', @@ -436,7 +437,12 @@ export default class PocomathInstance { } return } - // Nothing actually happens until we match a template parameter + // update the typeOf function + const imp = {} + imp[type] = {uses: new Set(['T']), does: ({T}) => () => `${base}<${T}>`} + this._installFunctions({typeOf: imp}) + + // Nothing else actually happens until we match a template parameter this.Templates[base] = {type, spec} } @@ -767,8 +773,9 @@ export default class PocomathInstance { const subsig = substituteInSig( needsig, theTemplateParam, instantiateFor) let resname = simplifiedDep - if (resname === 'self') resname = name - innerRefs[dep] = self._pocoresolve(resname, subsig) + if (resname == 'self') resname = name + innerRefs[dep] = self._pocoresolve( + resname, subsig, refs[simplifiedDep]) } else { innerRefs[dep] = refs[simplifiedDep] } @@ -782,7 +789,7 @@ export default class PocomathInstance { this._addTFimplementation( tf_imps, signature, {uses: outerUses, does: patch}) } - this._correctPartialSelfRefs(tf_imps) + this._correctPartialSelfRefs(name, tf_imps) const tf = this._typed(name, tf_imps) Object.defineProperty(this, name, {configurable: true, value: tf}) return tf @@ -845,7 +852,7 @@ export default class PocomathInstance { } else { // can bundle up func, and grab its signature if need be let destination = this[func] - if (needsig) { + if (destination &&needsig) { destination = this._pocoresolve(func, needsig) } refs[dep] = destination @@ -878,7 +885,7 @@ export default class PocomathInstance { imps[signature] = does(refs) } - _correctPartialSelfRefs(imps) { + _correctPartialSelfRefs(name, imps) { for (const aSignature in imps) { if (!(imps[aSignature].deferred)) continue const part_self_references = imps[aSignature].psr @@ -894,7 +901,7 @@ export default class PocomathInstance { // No exact match, try to get one that matches with // subtypes since the whole conversion thing in typed-function // is too complicated to reproduce - const foundSig = this._findSubtypeImpl(imps, neededSig) + const foundSig = this._findSubtypeImpl(name, imps, neededSig) if (foundSig) { corrected_self_references.push(foundSig) } else { @@ -936,7 +943,7 @@ export default class PocomathInstance { } const resultingTypes = new Set() for (const iType of instantiations) { - const resultType = this._maybeAddTemplateType(base, iType) + const resultType = this.instantiateTemplate(base, iType) if (resultType) resultingTypes.add(resultType) } return resultingTypes @@ -946,7 +953,7 @@ export default class PocomathInstance { * instantiator to the Types of this instance, if it hasn't happened already. * Returns the name of the type if added, false otherwise. */ - _maybeAddTemplateType(base, instantiator) { + instantiateTemplate(base, instantiator) { const wantsType = `${base}<${instantiator}>` if (wantsType in this.Types) return false // OK, need to generate the type from the template @@ -956,7 +963,7 @@ export default class PocomathInstance { const template = this.Templates[base].spec if (!template) { throw new Error( - `Implementor error in _maybeAddTemplateType ${base} ${instantiator}`) + `Implementor error in instantiateTemplate(${base}, ${instantiator})`) } const instantiatorSpec = this.Types[instantiator] let beforeTypes = [] @@ -1010,7 +1017,7 @@ export default class PocomathInstance { return wantsType } - _findSubtypeImpl(imps, neededSig) { + _findSubtypeImpl(name, imps, neededSig) { if (neededSig in imps) return neededSig let foundSig = false const typeList = typeListOfSignature(neededSig) @@ -1047,6 +1054,13 @@ export default class PocomathInstance { || this._subtypes[otherType].has(myType)) { continue } + if (otherType in this.Templates) { + if (this.instantiateTemplate(otherType, myType)) { + let dummy + dummy = this[name] // for side effects + return this._findSubtypeImpl(name, this._imps[name], neededSig) + } + } allMatch = false break } @@ -1058,17 +1072,28 @@ export default class PocomathInstance { return foundSig } - _pocoresolve(name, sig) { - const typedfunc = this[name] + _pocoresolve(name, sig, typedFunction) { + if (!this._typed.isTypedFunction(typedFunction)) { + typedFunction = this[name] + } let result = undefined try { - result = this._typed.find(typedfunc, sig, {exact: true}) + result = this._typed.find(typedFunction, sig, {exact: true}) } catch { } if (result) return result - const foundsig = this._findSubtypeImpl(this._imps[name], sig) - if (foundsig) return this._typed.find(typedfunc, foundsig) - return this._typed.find(typedfunc, sig) + const foundsig = this._findSubtypeImpl(name, this._imps[name], sig) + if (foundsig) return this._typed.find(typedFunction, foundsig) + // Make sure bundle is up-to-date: + typedFunction = this[name] + try { + result = this._typed.find(typedFunction, sig) + } catch { + } + if (result) return result + // total punt, revert to typed-function resolution on every call; + // hopefully this happens rarely: + return typedFunction } } diff --git a/src/generic/reducingOperation.mjs b/src/generic/reducingOperation.mjs index 101a8ec..e29baf1 100644 --- a/src/generic/reducingOperation.mjs +++ b/src/generic/reducingOperation.mjs @@ -4,6 +4,7 @@ export const reducingOperation = { 'undefined': () => u => u, 'undefined,...any': () => (u, rest) => u, 'any,undefined': () => (x, u) => u, + 'undefined,undefined': () => (u,v) => u, any: () => x => x, 'any,any,...any': ({ self diff --git a/src/number/sqrt.mjs b/src/number/sqrt.mjs index f6cc459..3017e82 100644 --- a/src/number/sqrt.mjs +++ b/src/number/sqrt.mjs @@ -1,14 +1,17 @@ export * from './Types/number.mjs' export const sqrt = { - number: ({config, complex, 'self(Complex)': complexSqrt}) => { - if (config.predictable || !complexSqrt) { + number: ({ + config, + 'complex(number,number)': cplx, + 'negate(number)': neg}) => { + if (config.predictable || !cplx) { 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)) + return cplx(0, Math.sqrt(neg(n))) } } } diff --git a/src/ops/floor.mjs b/src/ops/floor.mjs index e8897e8..71c8e70 100644 --- a/src/ops/floor.mjs +++ b/src/ops/floor.mjs @@ -7,7 +7,7 @@ import {Complex} from '../complex/Types/Complex.mjs' export const floor = { bigint: () => x => x, NumInt: () => x => x, // Because Pocomath isn't part of typed-function, or - GaussianInteger: () => x => x, // at least have access to the real + 'Complex': () => x => x, // at least have access to the real // typed-function parse, we unfortunately can't coalesce these into one // entry with type `bigint|NumInt|GaussianInteger` because they couldn't // be separately activated then @@ -17,7 +17,7 @@ export const floor = { return Math.floor(n) }, - Complex: Complex.promoteUnary.Complex, + 'Complex': Complex.promoteUnary['Complex'], // OK to include a type totally not in Pocomath yet, it'll never be // activated. diff --git a/src/tuple/Types/Tuple.mjs b/src/tuple/Types/Tuple.mjs index 0c6c0ae..d76d147 100644 --- a/src/tuple/Types/Tuple.mjs +++ b/src/tuple/Types/Tuple.mjs @@ -32,7 +32,12 @@ Tuple.installType('Tuple', { }) Tuple.promoteUnary = { - 'Tuple': ({'self(T)': me, tuple}) => t => tuple(...(t.elts.map(me))) + 'Tuple': ({ + 'self(T)': me, + tuple + }) => t => tuple(...(t.elts.map(x => me(x)))) // NOTE: this must use + // the inner arrow function to drop additional arguments that Array.map + // supplies, as otherwise the wrong signature of `me` might be used. } Tuple.promoteBinaryUnary = { diff --git a/test/_pocomath.mjs b/test/_pocomath.mjs index 1d7d1e9..38df18b 100644 --- a/test/_pocomath.mjs +++ b/test/_pocomath.mjs @@ -16,8 +16,8 @@ describe('The default full pocomath instance "math"', () => { 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') + assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex') + assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex') }) it('can subtract numbers', () => { @@ -105,11 +105,9 @@ describe('The default full pocomath instance "math"', () => { it('calculates multi-way gcds and lcms', () => { assert.strictEqual(math.gcd(30,105,42), 3) - assert.ok( - math.associate( - math.lcm( - math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)), - math.complex(1n,3n))) + const gaussianLCM = math.lcm( + math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)) + assert.strictEqual(math.associate(gaussianLCM, math.complex(1n,3n)), true) }) }) diff --git a/test/complex/_all.mjs b/test/complex/_all.mjs index e486fc3..801aa99 100644 --- a/test/complex/_all.mjs +++ b/test/complex/_all.mjs @@ -49,6 +49,15 @@ describe('complex', () => { assert.deepStrictEqual( math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)), math.complex(4n, 5n)) + // And now works for NumInt, too! + assert.deepStrictEqual( + math.gcd(math.complex(53,56), math.complex(47, -13)), + math.complex(4, 5)) + // But properly fails for general complex + assert.throws( + () => math.gcd(math.complex(5.3,5.6), math.complex(4.7, -1.3)), + TypeError + ) }) it('computes floor', () => { diff --git a/test/custom.mjs b/test/custom.mjs index 6a399b0..f6612ef 100644 --- a/test/custom.mjs +++ b/test/custom.mjs @@ -3,12 +3,14 @@ import math from '../src/pocomath.mjs' import PocomathInstance from '../src/core/PocomathInstance.mjs' import * as numbers from '../src/number/all.mjs' import * as numberAdd from '../src/number/add.mjs' +import * as numberZero from '../src/number/zero.mjs' import {add as genericAdd} from '../src/generic/arithmetic.mjs' import * as complex from '../src/complex/all.mjs' import * as complexAdd from '../src/complex/add.mjs' import * as complexNegate from '../src/complex/negate.mjs' import * as complexComplex from '../src/complex/complex.mjs' import * as bigintAdd from '../src/bigint/add.mjs' +import * as bigintZero from '../src/bigint/zero.mjs' import * as concreteSubtract from '../src/generic/subtract.concrete.mjs' import * as genericSubtract from '../src/generic/subtract.mjs' import extendToComplex from '../src/complex/extendToComplex.mjs' @@ -54,9 +56,9 @@ describe('A custom instance', () => { math.complex(-5, -1)) // And now floor has been activated for Complex as well, since the type // is present - assert.deepStrictEqual( - pm.floor(math.complex(1.9, 0)), - math.complex(1)) + const fracComplex = math.complex(1.9, 0) + const intComplex = math.complex(1) + assert.deepStrictEqual(pm.floor(fracComplex), intComplex) // And the chain functions refresh themselves: assert.deepStrictEqual( pm.chain(5).add(pm.chain(0).complex(7).value).value, math.complex(5,7)) @@ -65,10 +67,11 @@ describe('A custom instance', () => { it("can defer definition of (even used) types", () => { const dt = new PocomathInstance('Deferred Types') dt.install(numberAdd) + dt.install(numberZero) // for promoting numbers to complex, to fill in im dt.install({times: { 'number,number': () => (m,n) => m*n, - 'Complex,Complex': ({complex}) => (w,z) => { - return complex(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re) + 'Complex,Complex': ({'complex(T,T)': cplx}) => (w,z) => { + return cplx(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re) } }}) // complex type not present but should still be able to add numbers: @@ -82,6 +85,7 @@ describe('A custom instance', () => { it("can selectively import in cute ways", async function () { const cherry = new PocomathInstance('cherry') cherry.install(numberAdd) + cherry.install(numberZero) // for complex promotion await extendToComplex(cherry) cherry.install({add: genericAdd}) /* Now we have an instance that supports addition for number and complex @@ -124,12 +128,13 @@ describe('A custom instance', () => { inst.install(complexAdd) inst.install(complexComplex) inst.install(bigintAdd) + inst.install(bigintZero) // for complex promotion assert.strictEqual( inst.typeMerge(6n, inst.complex(3n, 2n)), - 'Merge to GaussianInteger') + 'Merge to Complex') assert.strictEqual( inst.typeMerge(3, inst.complex(4.5,2.1)), - 'Merge to Complex') + 'Merge to Complex') // The following is the current behavior, since 3 converts to 3+0i // which is technically the same Complex type as 3n+0ni. // This should clear up when Complex is templatized