diff --git a/src/complex/isReal.mjs b/src/complex/isReal.mjs new file mode 100644 index 0000000..57eb7ee --- /dev/null +++ b/src/complex/isReal.mjs @@ -0,0 +1,7 @@ +import Returns from '../core/Returns.mjs' +export * from './Types/Complex.mjs' + +export const isReal = { + 'Complex': ({'equal(T,T)': eq, 'add(T,T)': plus}) => Returns( + 'boolean', z => eq(z.re, plus(z.re, z.im))) +} diff --git a/src/complex/native.mjs b/src/complex/native.mjs index db81c53..1668743 100644 --- a/src/complex/native.mjs +++ b/src/complex/native.mjs @@ -18,5 +18,6 @@ export {quaternion} from './quaternion.mjs' export {quotient} from './quotient.mjs' export {roundquotient} from './roundquotient.mjs' export {sqrt} from './sqrt.mjs' +export {sqrtc} from './sqrtc.mjs' export {zero} from './zero.mjs' diff --git a/src/complex/polynomialRoot.mjs b/src/complex/polynomialRoot.mjs new file mode 100644 index 0000000..fc7c255 --- /dev/null +++ b/src/complex/polynomialRoot.mjs @@ -0,0 +1,65 @@ +import Returns from '../core/Returns.mjs' +export * from './Types/Complex.mjs' + +export const polynomialRoot = { + 'Complex,...Complex': ({ + T, + 'tuple(...Complex)': tupCplx, + 'tuple(...T)': tupReal, + 'isZero(Complex)': zero, + 'complex(T)': C, + 'multiply(Complex,Complex)': mul, + 'divide(Complex,Complex)': div, + 'negate(Complex)': neg, + 'isReal(Complex)': real, + 'equalTT(Complex, Complex)': eq, + 'subtract(Complex, Complex)': sub, + 'sqrtc(Complex)': sqt, + }) => Returns(`Tuple<${T}>|Tuple>`, (constant, rest) => { + // helper to convert results to appropriate tuple type + const typedTup = arr => { + if (arr.every(real)) { + return tupReal.apply(tupReal, arr.map(z => z.re)) + } + return tupCplx.apply(tupCplx, arr) + } + + const coeffs = [constant, ...rest] + while (coeffs.length > 0 && zero(coeffs[coeffs.length - 1])) { + coeffs.pop() + } + if (coeffs.length < 2) { + } + switch (coeffs.length) { + case 0: case 1: + throw new RangeError( + `Polynomial [${constant}, ${rest}] must have at least one` + + 'non-zero non-constant coefficient') + case 2: // linear + return typedTup([neg(div(coeffs[0], coeffs[1]))]) + case 3: { // quadratic + const [c, b, a] = coeffs + console.log('solving', a, b, c) + const denom = mul(C(2), a) + const d1 = mul(b, b) + const d2 = mul(C(4), mul(a, c)) + console.log('Whoa', denom, d1, d2) + if (eq(d1, d2)) { + console.log('Hello', b, denom, div(neg(b), denom)) + return typedTup([div(neg(b), denom)]) + } + let discriminant = sqt(sub(d1, d2)) + console.log('Uhoh', discriminant) + console.log('Roots', div(sub(discriminant, b), denom), div(sub(neg(discriminant), b), denom)) + return typedTup([ + div(sub(discriminant, b), denom), + div(sub(neg(discriminant), b), denom) + ]) + } + default: + throw new RangeError( + 'only implemented for cubic or lower-order polynomials, ' + + `not ${JSON.stringify(coeffs)}`) + } + }) +} diff --git a/src/complex/sqrt.mjs b/src/complex/sqrt.mjs index 7f4044b..cee5278 100644 --- a/src/complex/sqrt.mjs +++ b/src/complex/sqrt.mjs @@ -4,49 +4,30 @@ export * from './Types/Complex.mjs' export const sqrt = { 'Complex': ({ config, + 'sqrtc(Complex)': predictableSqrt, '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, - 'absquare(Complex)': absqC, - 'subtract(T,T)': sub }) => { - let baseReturns = returnTypeOf(me) - if (baseReturns.includes('|')) { - // Bit of a hack, because it is relying on other implementations - // to list the "typical" value of sqrt first - baseReturns = baseReturns.split('|', 1)[0] - } - + if (config.checkingDependency) return undefined + const complexReturns = returnTypeOf(predictableSqrt) + const baseReturns = complexReturns.slice(8, -1); // Complex if (config.predictable) { - return Returns(`Complex<${baseReturns}>`, z => { - const reOne = uno(z.re) - if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re)) - const reTwo = plus(reOne, reOne) - const myabs = me(absqC(z)) - return cplxB( - mult(sgn(z.im), me(div(plus(myabs, z.re), reTwo))), - me(div(sub(myabs, z.re), reTwo)) - ) - }) + return Returns(complexReturns, z => predictableSqrt(z)) } return Returns( `Complex<${baseReturns}>|${baseReturns}|undefined`, z => { - const reOne = uno(z.re) - if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re) - const reTwo = plus(reOne, reOne) - const myabs = me(absqC(z)) - const reSqrt = me(div(plus(myabs, z.re), reTwo)) - const imSqrt = me(div(sub(myabs, z.re), reTwo)) - if (reSqrt === undefined || imSqrt === undefined) return undefined - return cplxB(mult(sgn(z.im), reSqrt), imSqrt) + let complexSqrt + try { + complexSqrt = predictableSqrt(z) + } catch (e) { + return undefined + } + if (complexSqrt.re === undefined || complexSqrt.im === undefined) { + return undefined + } + if (isZ(complexSqrt.im)) return complexSqrt.re + return complexSqrt } ) } diff --git a/src/complex/sqrtc.mjs b/src/complex/sqrtc.mjs new file mode 100644 index 0000000..3a7ea1d --- /dev/null +++ b/src/complex/sqrtc.mjs @@ -0,0 +1,42 @@ +import {Returns, returnTypeOf} from '../core/Returns.mjs' +export * from './Types/Complex.mjs' + +export const sqrtc = { + 'Complex': ({ + T, + 'isZero(T)': isZ, + 'sign(T)': sgn, + 'one(T)': uno, + 'add(T,T)': plus, + 'complex(T)': cplxU, + 'complex(T,T)': cplxB, + 'multiply(T,T)': mult, + 'sqrt(T)': sqt, + 'divide(T,T)': div, + 'absquare(Complex)': absqC, + 'subtract(T,T)': sub + }) => { + if (isZ.checkingDependency) return undefined + let baseReturns = returnTypeOf(sqt) + if (baseReturns.includes('|')) { + // Bit of a hack, because it is relying on other implementations + // to list the "typical" value of sqrt first + baseReturns = baseReturns.split('|', 1)[0] + } + return Returns(`Complex<${baseReturns}>`, z => { + const reOne = uno(z.re) + if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(sqt(z.re)) + const myabs = sqt(absqC(z)) + const reTwo = plus(reOne, reOne) + const reQuot = div(plus(myabs, z.re), reTwo) + const imQuot = div(sub(myabs, z.re), reTwo) + if (reQuot === undefined || imQuot === undefined) { + throw new TypeError(`Cannot compute sqrt of ${z.re} + {z.im}i`) + } + return cplxB( + mult(sgn(z.im), sqt(div(plus(myabs, z.re), reTwo))), + sqt(div(sub(myabs, z.re), reTwo)) + ) + }) + } +} diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 17b65ae..f28bf91 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -7,6 +7,12 @@ import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs' const anySpec = {} // fixed dummy specification of 'any' type +/* Like `.some(predicate)` but for collections */ +function exists(collection, predicate) { + for (const item of collection) if (predicate(item)) return true; + return false; +} + /* Template/signature parsing stuff; should probably be moved to a * separate file, but it's a bit interleaved at the moment */ @@ -98,6 +104,7 @@ export default class PocomathInstance { static reserved = new Set([ 'chain', 'config', + 'convert', 'importDependencies', 'install', 'installType', @@ -138,8 +145,21 @@ export default class PocomathInstance { const me = this this._typed.onMismatch = (name, args, sigs) => { if (me._invalid.has(name)) { + if (this._fixing === name) { + this._fixingCount += 1 + if (this._fixingCount > this._maxDepthSeen + 2) { + throw new ReferenceError( + `Infinite descent rebuilding ${name} on ${args}`) + } + } else { + this._fixingCount = 0 + } // rebuild implementation and try again - return me[name](...args) + const lastFixing = this._fixing + this._fixing = name + const value = me[name](...args) + this._fix = lastFixing + return value } const metaversion = me._meta[name] if (metaversion) { @@ -185,6 +205,8 @@ export default class PocomathInstance { this._plainFunctions = new Set() // the names of the plain functions this._chainRepository = {} // place to store chainified functions this.joinTypes = this.joinTypes.bind(me) + // Provide access to typed function conversion: + this.convert = this._typed.convert.bind(this._typed) } /** @@ -763,45 +785,39 @@ export default class PocomathInstance { /** * Reset an operation to require creation of typed-function, * and if it has no implementations so far, set them up. + * name is the name of the operation, badType is a type that has been + * invalidated, and reasons is a set of specific operations/signatures + * that have been invalidated */ - _invalidate(name, reason) { + _invalidate(name, badType = '', reasons = new Set()) { if (!(name in this._imps)) { this._imps[name] = {} this._TFimps[name] = {} this._metaTFimps[name] = {} } - if (reason) { - // Make sure no TF imps that depend on reason remain: - for (const [signature, behavior] of Object.entries(this._imps[name])) { - let invalidated = false - if (reason.charAt(0) === ':') { - const badType = reason.slice(1) - if (signature.includes(badType)) invalidated = true + // Go through each TF imp and invalidate it if need be + for (const [signature, imp] of Object.entries(this._TFimps[name])) { + if (imp.deferred + || (badType && signature.includes(badType)) + || exists(imp.uses, dep => { + const [func, sig] = dep.split(/[()]/) + return reasons.has(dep) + || (reasons.has(func) && !(sig in this._TFimps[func])) + })) { + // Invalidate this implementation: + delete this._TFimps[name][signature] + const behavior = imp.fromBehavior + if (behavior.explicit) { + behavior.resolved = false } else { - for (const dep of behavior.uses) { - if (dep.includes(reason)) { - invalidated = true - break - } - } - } - if (invalidated) { - if (behavior.explicit) { - if (behavior.resolved) delete this._TFimps[signature] - behavior.resolved = false - } else { - for (const fullSig - of Object.values(behavior.hasInstantiations)) { - delete this._TFimps[fullSig] - } - behavior.hasInstantiations = {} - } + delete behavior.hasInstantiations[imp.instance] } + reasons.add(`${name}(${signature})`) } } if (this._invalid.has(name)) return this._invalid.add(name) - this._invalidateDependents(name) + this._invalidateDependents(name, badType, reasons) const self = this Object.defineProperty(this, name, { configurable: true, @@ -815,11 +831,14 @@ export default class PocomathInstance { /** * Invalidate all the dependents of a given property of the instance + * reasons is a set of invalidated signatures */ - _invalidateDependents(name) { + _invalidateDependents(name, badType, reasons = new Set()) { + if (name.charAt(0) === ':') badType = name.slice(1) + else reasons.add(name) if (name in this._affects) { for (const ancestor of this._affects[name]) { - this._invalidate(ancestor, name) + this._invalidate(ancestor, badType, reasons) } } } @@ -860,7 +879,7 @@ export default class PocomathInstance { for (const [rawSignature, behavior] of usableEntries) { if (behavior.explicit) { if (!(behavior.resolved)) { - this._addTFimplementation(tf_imps, rawSignature, behavior) + this._addTFimplementation(name, tf_imps, rawSignature, behavior) tf_imps[rawSignature]._pocoSignature = rawSignature behavior.resolved = true } @@ -880,11 +899,18 @@ export default class PocomathInstance { } /* First, add the known instantiations, gathering all types needed */ if (ubType) behavior.needsInstantiations.add(ubType) + const nargs = typeListOfSignature(rawSignature).length let instantiationSet = new Set() const ubTypes = new Set() if (!ubType) { // Collect all upper-bound types for this signature for (const othersig in imps) { + const otherNargs = typeListOfSignature(othersig).length + if (nargs !== otherNargs) { + // crude criterion that it won't match, that ignores + // rest args, but hopefully OK for prototype + continue + } const thisUB = upperBounds.exec(othersig) if (thisUB) ubTypes.add(thisUB[2]) let basesig = othersig.replaceAll(templateCall, '') @@ -907,14 +933,13 @@ export default class PocomathInstance { for (const instType of behavior.needsInstantiations) { instantiationSet.add(instType) const otherTypes = - ubType ? this.subtypesOf(instType) : this._priorTypes[instType] + ubType ? this.subtypesOf(instType) : this._priorTypes[instType] for (const other of otherTypes) { if (!(this._atOrBelowSomeType(other, ubTypes))) { instantiationSet.add(other) } } } - /* Prevent other existing signatures from blocking use of top-level * templates via conversions: */ @@ -953,7 +978,8 @@ export default class PocomathInstance { } } for (const instType of instantiationSet) { - this._instantiateTemplateImplementation(name, rawSignature, instType) + this._instantiateTemplateImplementation( + name, rawSignature, instType) } /* Now add the catchall signature */ /* (Not needed if if it's a bounded template) */ @@ -1008,6 +1034,7 @@ export default class PocomathInstance { `Type inference failed for argument ${j} of ${name}`) } if (argType === 'any') { + console.log('INCOMPATIBLE ARGUMENTS are', args) throw TypeError( `In call to ${name}, ` + 'incompatible template arguments:' @@ -1065,12 +1092,19 @@ export default class PocomathInstance { // But possibly since this resolution was grabbed, the proper // instantiation has been added (like if there are multiple // uses in the implementation of another method. - if (!(behavior.needsInstantiations.has(instantiateFor))) { - behavior.needsInstantiations.add(instantiateFor) + let whatToDo + if (!(instantiateFor in behavior.hasInstantiations)) { + const newImp = self._instantiateTemplateImplementation( + name, rawSignature, instantiateFor) + if (newImp) { + whatToDo = {fn: newImp, implementation: newImp} + } self._invalidate(name) } const brandNewMe = self[name] - const whatToDo = self._typed.resolve(brandNewMe, args) + const betterToDo = self._typed.resolve(brandNewMe, args) + whatToDo = betterToDo || whatToDo + // We can access return type information here // And in particular, if it might be a template, we should try to // instantiate it: @@ -1084,8 +1118,13 @@ export default class PocomathInstance { } if (whatToDo === lastWhatToDo) { throw new Error( - `Infinite recursion in resolving $name called on` - + args.map(x => x.toString()).join(',')) + `Infinite recursion in resolving ${name} called on ` + + args.map(x => + (typeof x === 'object' + ? JSON.stringify(x) + : x.toString()) + ).join(', ') + + ` inferred to be ${wantSig}`) } lastWhatToDo = whatToDo const retval = whatToDo.implementation(...args) @@ -1103,7 +1142,9 @@ export default class PocomathInstance { // correct return type a priori. Deferring because unclear what // aspects will be merged into typed-function. this._addTFimplementation( - meta_imps, signature, {uses: new Set(), does: patch}) + name, meta_imps, signature, + {uses: new Set(), does: patch}, + behavior) behavior.resolved = true } this._correctPartialSelfRefs(name, tf_imps) @@ -1112,6 +1153,9 @@ export default class PocomathInstance { // ditch the signature: const badSigs = new Set() for (const sig in tf_imps) { + if (!tf_imps[sig].uses) { + throw new ReferenceError(`MONKEY WRENCH: ${name} ${sig}`) + } for (const type of typeListOfSignature(sig)) { if (this._maybeInstantiate(type) === undefined) { badSigs.add(sig) @@ -1232,10 +1276,12 @@ export default class PocomathInstance { return behavior.does(innerRefs) } const tf_imps = this._TFimps[name] - this._addTFimplementation(tf_imps, signature, {uses, does: patch}) + this._addTFimplementation( + name, tf_imps, signature, {uses, does: patch}, behavior, instanceType) tf_imps[signature]._pocoSignature = templateSignature tf_imps[signature]._pocoInstance = instanceType behavior.hasInstantiations[instanceType] = signature + behavior.needsInstantiations.add(instanceType) // once we have it, keep it return tf_imps[signature] } @@ -1243,10 +1289,17 @@ export default class PocomathInstance { * to typed-function implementations and inserts the result into plain * object imps */ - _addTFimplementation(imps, signature, behavior) { - const {uses, does} = behavior + _addTFimplementation( + name, imps, signature, specificBehavior, fromImp, asInstance) + { + if (!fromImp) fromImp = specificBehavior + const {uses, does} = specificBehavior if (uses.length === 0) { const implementation = does() + implementation.uses = uses + implementation.fromInstance = this + implementation.fromBehavior = fromImp + implementation.instance = asInstance // could do something with return type information here imps[signature] = implementation return @@ -1274,18 +1327,25 @@ export default class PocomathInstance { */ if (needsig in imps && typeof imps[needsig] == 'function') { refs[dep] = imps[needsig] + continue + } + if (full_self_referential) { + throw new SyntaxError( + 'typed-function does not support mixed full and ' + + 'partial self-reference') + } + const needTypes = typesOfSignature(needsig) + const mergedTypes = Object.assign( + {}, this.Types, this.Templates) + if (subsetOfKeys(needTypes, mergedTypes)) { + func = name // just resolve it in limbo } else { - if (full_self_referential) { - throw new SyntaxError( - 'typed-function does not support mixed full and ' - + 'partial self-reference') - } - const needTypes = typesOfSignature(needsig) - const mergedTypes = Object.assign( - {}, this.Types, this.Templates) - if (subsetOfKeys(needTypes, mergedTypes)) { - part_self_references.push(needsig) - } + // uses an unknown type, so will get an undefined impl + console.log( + 'WARNING: partial self-reference for', name, 'to', + needsig, 'uses an unknown type') + refs[dep] = undefined + continue } } else { if (part_self_references.length) { @@ -1294,65 +1354,49 @@ export default class PocomathInstance { + 'partial self-reference') } full_self_referential = true - } - } else { - if (this[func] === 'limbo') { - /* We are in the midst of bundling func */ - let fallback = true - /* So the first thing we can do is try the tf_imps we are - * accumulating: - */ - if (needsig) { - let typedUniverse - let tempTF - if (Object.keys(this._TFimps[func]).length > 0) { - typedUniverse = this._typed - tempTF = typedUniverse('dummy_' + func, this._TFimps[func]) - } else { - typedUniverse = this._metaTyped - tempTF = typedUniverse( - 'dummy_' + func, this._metaTFimps[func]) - } - let result = undefined - try { - result = typedUniverse.find(tempTF, needsig, {exact: true}) - } catch {} - if (result) { - refs[dep] = result - fallback = false - } - } - if (fallback) { - /* Either we need the whole function or the signature - * we need is not available yet, so we 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 - const redirect = function () { // is this the most efficient? - return self[func].apply(this, arguments) - } - Object.defineProperty(redirect, 'name', {value: func}) - Object.defineProperty(redirect, 'fromInstance', {value: this}) - refs[dep] = redirect - } - } else { - // can bundle up func, and grab its signature if need be - let destination = this[func] - if (destination && needsig) { - destination = this.resolve(func, needsig) - } - if (!destination) { - // Unresolved reference. This is allowed so that - // you can bundle up just some portions of the library, - // but let's warn. - console.log( - 'WARNING: No definition found for dependency', - dep, 'needed by a function with signature', signature) - } - refs[dep] = destination + continue } } + if (this[func] === 'limbo') { + /* We are in the midst of bundling func (which may be ourself) */ + /* So the first thing we can do is try the tf_imps we are + * accumulating: + */ + if (needsig) { + const candidate = this.resolve(func, needsig) + if (typeof candidate === 'function') { + refs[dep] = candidate + continue + } + } + /* Either we need the whole function or the signature + * we need is not available yet, so we 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 + const redirect = function () { // is this the most efficient? + return self[func].apply(this, arguments) + } + Object.defineProperty(redirect, 'name', {value: func}) + Object.defineProperty(redirect, 'fromInstance', {value: this}) + refs[dep] = redirect + continue + } + // can bundle up func, and grab its signature if need be + let destination = this[func] + if (needsig) { + destination = this.resolve(func, needsig) + } + if (!destination) { + // Unresolved reference. This is allowed so that + // you can bundle up just some portions of the library, + // but let's warn. + console.log( + 'WARNING: No definition found for dependency', + dep, 'needed by', name, '(', signature, ')') + } + refs[dep] = destination } if (full_self_referential) { imps[signature] = this._typed.referToSelf(self => { @@ -1360,9 +1404,19 @@ export default class PocomathInstance { const implementation = does(refs) Object.defineProperty(implementation, 'name', {value: does.name}) implementation.fromInstance = this + implementation.uses = uses + implementation.instance = asInstance + implementation.fromBehavior = fromImp // What are we going to do with the return type info in here? return implementation }) + imps[signature].uses = uses + if (!uses) { + throw new ReferenceError(`NANH uses for ${signature} from ${fromImp}`) + } + imps[signature].fromInstance = this + imps[signature].instance = asInstance + imps[signature].fromBehavior = fromImp return } if (part_self_references.length) { @@ -1377,13 +1431,19 @@ export default class PocomathInstance { deferred: true, builtRefs: refs, sigDoes: does, + sigUses: uses, fromInstance: this, + fromBehavior: fromImp, + instance: asInstance, psr: part_self_references } return } const implementation = does(refs) implementation.fromInstance = this + implementation.fromBehavior = fromImp + implementation.instance = asInstance + implementation.uses = uses // could do something with return type information here? imps[signature] = implementation } @@ -1457,6 +1517,10 @@ export default class PocomathInstance { refs[`self(${remaining_self_references[i]})`] = impls[i] } const implementation = does(refs) + implementation.fromInstance = deferral.fromInstance + implementation.fromBehavior = deferral.fromBehavior + implementation.instance = deferral.instance + implementation.uses = deferral.sigUses // What will we do with the return type info in here? return implementation } @@ -1467,6 +1531,9 @@ export default class PocomathInstance { imps[aSignature]._pocoSignature = deferral._pocoSignature imps[aSignature]._pocoInstance = deferral._pocoInstance imps[aSignature].fromInstance = deferral.fromInstance + imps[aSignature].fromBehavior = deferral.fromBehavior + imps[aSignature].instance = deferral.instance + imps[aSignature].uses = deferral.sigUses } } } @@ -1576,7 +1643,8 @@ export default class PocomathInstance { return wantsType }) - _findSubtypeImpl(name, imps, neededSig) { + _findSubtypeImpl(name, imps, neededSig, raw = false) { + const detemplate = !raw if (neededSig in imps) return neededSig let foundSig = false const typeList = typeListOfSignature(neededSig) @@ -1589,16 +1657,16 @@ export default class PocomathInstance { let myType = typeList[k] let otherType = otherTypeList[k] if (otherType === theTemplateParam) { - otherTypeList[k] = paramBound + if (detemplate) otherTypeList[k] = paramBound otherType = paramBound } if (otherType === restTemplateParam) { - otherTypeList[k] = `...${paramBound}` + if (detemplate) otherTypeList[k] = `...${paramBound}` otherType = paramBound } const adjustedOtherType = otherType.replaceAll(templateCall, '') if (adjustedOtherType !== otherType) { - otherTypeList[k] = adjustedOtherType + if (detemplate) otherTypeList[k] = adjustedOtherType otherType = adjustedOtherType } if (myType.slice(0,3) === '...') myType = myType.slice(3) @@ -1607,8 +1675,10 @@ export default class PocomathInstance { if (otherBound) { paramBound = otherBound[2] otherType = paramBound - otherTypeList[k] = otherBound[1].replaceAll( - theTemplateParam, paramBound) + if (detemplate) { + otherTypeList[k] = otherBound[1].replaceAll( + theTemplateParam, paramBound) + } } if (otherType === 'any') continue if (otherType === UniversalType) continue @@ -1619,7 +1689,7 @@ export default class PocomathInstance { if (this.instantiateTemplate(otherType, myType)) { let dummy dummy = this[name] // for side effects - return this._findSubtypeImpl(name, this._imps[name], neededSig) + return this._findSubtypeImpl(name, this._imps[name], neededSig, raw) } } if (!(otherType in this.Types)) { @@ -1643,7 +1713,7 @@ export default class PocomathInstance { typedFunction = this[name] } const haveTF = this._typed.isTypedFunction(typedFunction) - && !(typedFunction.isMeta) + && !(typedFunction.isMeta) if (haveTF) { // First try a direct match let result @@ -1658,6 +1728,10 @@ export default class PocomathInstance { of typedFunction._typedFunctionData.signatureMap) { let allMatched = true const implTypes = typeListOfSignature(implSig) + if (implTypes.length > wantTypes.length) { + // Not enough arguments for that implementation + continue + } for (let i = 0; i < wantTypes.length; ++i) { const implIndex = Math.min(i, implTypes.length - 1) let implType = implTypes[implIndex] @@ -1682,7 +1756,7 @@ export default class PocomathInstance { } } if (!(this._imps[name])) return undefined - const foundsig = this._findSubtypeImpl(name, this._imps[name], sig) + const foundsig = this._findSubtypeImpl(name, this._imps[name], sig, 'raw') if (foundsig) { if (haveTF) { try { @@ -1690,19 +1764,74 @@ export default class PocomathInstance { } catch { } } - try { - return this._metaTyped.findSignature(this._meta[name], foundsig) - } catch { + const instantiationMatcher = + '^' + + substituteInSignature(foundsig, theTemplateParam, '(.*)') + .replaceAll(UniversalType, '(.*)') + + '$' + const instanceMatch = sig.match(instantiationMatcher) + let possibleInstantiator = false + if (instanceMatch) { + possibleInstantiator = instanceMatch[1] + for (let i = 2; i < instanceMatch.length; ++i) { + if (possibleInstantiator !== instanceMatch[i]) { + possibleInstantiator = false + break + } + } + } + if (possibleInstantiator) { + const behavior = this._imps[name][foundsig] + let newInstance + if (behavior) { + if (!(possibleInstantiator in behavior.hasInstantiations)) { + newInstance = this._instantiateTemplateImplementation( + name, foundsig, possibleInstantiator) + } else { + // OK, so we actually have the instantiation. Let's get it + newInstance = this._TFimps[name][sig] + } + // But we may not have taken advantage of conversions + this._invalidate(name) + const tryAgain = this[name] + let betterInstance + if (this._typed.isTypedFunction(tryAgain)) { + betterInstance = this._typed.findSignature(tryAgain, sig) + } + if (betterInstance) { + newInstance = betterInstance + } else { + newInstance = { + fn: newInstance, + implementation: newInstance + } + } + if (newInstance) return newInstance + } + } + const catchallSig = this._findSubtypeImpl(name, this._imps[name], sig) + if (catchallSig !== foundsig) { + try { + return this._metaTyped.findSignature( + this._meta[name], catchallSig) + } catch { + } } // We have an implementation but not a typed function. Do the best // we can: - const foundImpl = this._imps[name][foundsig] + const restoredSig = foundsig.replaceAll('ground', theTemplateParam) + const foundImpl = this._imps[name][restoredSig] const needs = {} for (const dep of foundImpl.uses) { - const [base, sig] = dep.split('()') - needs[dep] = this.resolve(base, sig) + const [base, sig] = dep.split(/[()]/) + if (sig) { + needs[dep] = this.resolve(base, sig) + } else { + needs[dep] = this[dep] + } } const pseudoImpl = foundImpl.does(needs) + pseudoImpl.fromInstance = this return {fn: pseudoImpl, implementation: pseudoImpl} } // Hmm, no luck. Make sure bundle is up-to-date and retry: diff --git a/src/core/extractors.mjs b/src/core/extractors.mjs index 0db3c0f..f463cd1 100644 --- a/src/core/extractors.mjs +++ b/src/core/extractors.mjs @@ -6,7 +6,7 @@ export function dependencyExtractor(destinationSet) { return new Proxy({}, { get: (target, property) => { destinationSet.add(property) - return {} + return {checkingDependency: true} } }) } diff --git a/src/number/add.mjs b/src/number/add.mjs index 41791cf..5c363d1 100644 --- a/src/number/add.mjs +++ b/src/number/add.mjs @@ -4,5 +4,5 @@ export * from './Types/number.mjs' export const add = { // Note the below assumes that all subtypes of number that will be defined // are closed under addition! - 'T:number, T': ({T}) => Returns(T, (m,n) => m+n) + 'T:number,T': ({T}) => Returns(T, (m,n) => m+n) } diff --git a/test/complex/_polynomialRoot.mjs b/test/complex/_polynomialRoot.mjs new file mode 100644 index 0000000..1224bcf --- /dev/null +++ b/test/complex/_polynomialRoot.mjs @@ -0,0 +1,34 @@ +import assert from 'assert' +import math from '../../src/pocomath.mjs' + +describe('polynomialRoot', () => { + it('should solve a linear equation with real coefficients', function () { + assert.deepEqual(math.polynomialRoot(6, 3), math.tuple(-2)) + assert.deepEqual( + math.polynomialRoot(math.complex(-3, 2), 2), + math.tuple(math.complex(1.5, -1))) + assert.deepEqual( + math.polynomialRoot(math.complex(3, 1), math.complex(-1, -1)), + math.tuple(math.complex(2, -1))) + }) + // Should be safe now to capture the functions: + const complex = math.complex + const pRoot = math.polynomialRoot + const tup = math.tuple + it('should solve a quadratic equation with a double root', function () { + assert.deepEqual(pRoot(4, 4, 1), tup(-2)) + assert.deepEqual( + pRoot(complex(0, 2), complex(2, 2), 1), tup(complex(-1, -1))) + }) + it('should solve a quadratic with two distinct roots', function () { + assert.deepEqual(pRoot(-3, 2, 1), tup(1, -3)) + assert.deepEqual(pRoot(-2, 0, 1), tup(math.sqrt(2), -math.sqrt(2))) + assert.deepEqual( + pRoot(4, 2, 1), + tup(complex(-1, math.sqrt(3)), complex(-1, -math.sqrt(3)))) + assert.deepEqual( + pRoot(complex(3, 1), -3, 1), tup(complex(1, 1), complex(2, -1))) + }) + + +})