diff --git a/src/complex/abs.mjs b/src/complex/abs.mjs index 30034ed..53a2464 100644 --- a/src/complex/abs.mjs +++ b/src/complex/abs.mjs @@ -8,13 +8,8 @@ export const abs = { 'absquare(T)': baseabsq, 'absquare(Complex)': absq }) => { - const pm = sqrt.fromInstance - if (typeof pm === 'undefined') { - // Just checking for the dependencies, return value is irrelevant - return undefined - } const midType = returnTypeOf(baseabsq) - const sqrtImp = pm.resolve('sqrt', midType, sqrt) + const sqrtImp = sqrt.fromInstance.resolve('sqrt', midType, sqrt) let retType = returnTypeOf(sqrtImp) if (retType.includes('|')) { // This is a bit of a hack, as it relies on all implementations of diff --git a/src/complex/absquare.mjs b/src/complex/absquare.mjs index b013c4e..ab0194c 100644 --- a/src/complex/absquare.mjs +++ b/src/complex/absquare.mjs @@ -9,13 +9,9 @@ export const absquare = { // we extract the needed implementation below. 'self(T)': absq }) => { - const pm = add.fromInstance - if (typeof pm === 'undefined') { - // Just checking the dependencies, return value irrelevant - return undefined - } const midType = returnTypeOf(absq) - const addImp = pm.resolve('add', `${midType},${midType}`, add) + const addImp = add.fromInstance.resolve( + 'add', `${midType},${midType}`, add) return Returns( returnTypeOf(addImp), z => addImp(absq(z.re), absq(z.im))) } diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 3ac6f94..4b46752 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -7,11 +7,76 @@ import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs' const anySpec = {} // fixed dummy specification of 'any' type +/* Template/signature parsing stuff; should probably be moved to a + * separate file, but it's a bit interleaved at the moment + */ + const theTemplateParam = 'T' // First pass: only allow this one exact parameter const restTemplateParam = `...${theTemplateParam}` const templateCall = `<${theTemplateParam}>` const templateFromParam = 'U' // For defining covariant conversions +/* returns the pair [base, instance] for a template type. If the type + * is not a template, instance is undefined + */ +const templatePattern = /^\s*([^<\s]*)\s*<\s*(\S*)\s*>\s*$/ +function splitTemplate(type) { + if (!(type.includes('<'))) return [type, undefined] + const results = templatePattern.exec(type) + return [results[1], results[2]] +} +/* Returns the instance such that type is template instantiated for that + * instance. + */ +function whichInstance(type, template) { + if (template === theTemplateParam) return type + if (type === template) return '' + if (!(template.includes(templateCall))) { + throw new TypeError( + `Type ${template} is not a template, so can't produce ${type}`) + } + const [typeBase, typeInstance] = splitTemplate(type) + if (!typeInstance) { + throw new TypeError( + `Type ${type} not from a template, so isn't instance of ${template}`) + } + const [tempBase, tempInstance] = splitTemplate(template) + if (typeBase !== tempBase) { + throw new TypeError( + `Type ${type} has wrong top-level base to be instance of ${template}`) + } + return whichInstance(typeInstance, tempInstance) +} +/* Same as above, but for signatures */ +function whichSigInstance(sig, tempsig) { + const sigTypes = typeListOfSignature(sig) + const tempTypes = typeListOfSignature(tempsig) + const sigLength = sigTypes.length + if (sigLength === 0) { + throw new TypeError("No types in signature, so can't determine instance") + } + if (sigLength !== tempTypes.length) { + throw new TypeError(`Signatures ${sig} and ${tempsig} differ in length`) + } + let maybeInstance = whichInstance(sigTypes[0], tempTypes[0]) + for (let i = 1; i < sigLength; ++i) { + const currInstance = whichInstance(sigTypes[i], tempTypes[i]) + if (maybeInstance) { + if (currInstance && currInstance !== maybeInstance) { + throw new TypeError( + `Inconsistent instantiation of ${sig} from ${tempsig}`) + } + } else { + maybeInstance = currInstance + } + } + if (!maybeInstance) { + throw new TypeError( + `Signature ${sig} identical to ${tempsig}, not an instance`) + } + return maybeInstance +} + /* Returns a new signature just like sig but with the parameter replaced by * the type */ @@ -202,7 +267,10 @@ export default class PocomathInstance { const stdimps = {} for (const [signature, does] of Object.entries(spec)) { const uses = new Set() - does(dependencyExtractor(uses)) + try { + does(dependencyExtractor(uses)) + } catch { + } stdimps[signature] = {uses, does} } stdFunctions[item] = stdimps @@ -334,7 +402,7 @@ export default class PocomathInstance { * the corresponding changes to the _typed object immediately */ installType = Returns('void', function(type, spec) { - const parts = type.split(/[<,>]/) + const parts = type.split(/[<,>]/).map(s => s.trim()) if (this._templateParam(parts[0])) { throw new SyntaxError( `Type name '${type}' reserved for template parameter`) @@ -400,14 +468,14 @@ export default class PocomathInstance { } /* Add the conversion in the metaverse if need be: */ - const toParts = nextSuper.split('<', 2) - if (toParts.length > 1) { - const fromParts = from.split('<', 2) - if (fromParts.length === 1 || fromParts[0] !== toParts[0]) { + const [toBase, toInstance] = splitTemplate(nextSuper) + if (toInstance) { + const [fromBase, fromInstance] = splitTemplate(from) + if (!fromBase || fromBase !== toBase) { this._metafy(from) try { this._metaTyped.addConversion( - {from, to: toParts[0], convert: spec.from[from]}) + {from, to: toBase, convert: spec.from[from]}) } catch { } } @@ -438,12 +506,12 @@ export default class PocomathInstance { }) this._invalidateDependents(':' + nextSuper) /* Add the conversion in the metaverse if need be: */ - const toParts = nextSuper.split('<', 2) - if (toParts.length > 1 && base !== toParts[0]) { + const [toBase, toInstance] = splitTemplate(nextSuper) + if (toInstance && base !== toBase) { this._metafy(type) this._metaTyped.addConversion({ from: type, - to: toParts[0], + to: toBase, convert: this.Types[to].from[fromtype] }) } @@ -559,7 +627,7 @@ export default class PocomathInstance { /* Used internally to install a template type */ _installTemplateType(type, spec) { - const base = type.split('<')[0] + const [base] = splitTemplate(type) /* For now, just allow a single template per base type; that * might need to change later: */ @@ -760,7 +828,7 @@ export default class PocomathInstance { let keep = true for (const type of typesOfSignature(entry[0])) { if (type in this.Types) continue - const baseType = type.split('<')[0] + const [baseType] = splitTemplate(type) if (baseType in this.Templates) continue keep = false break @@ -857,7 +925,7 @@ export default class PocomathInstance { othertype, theTemplateParam, '') let otherTypeCollection = [othertype] if (testType !== othertype) { - const base = othertype.split('<',1)[0] + const [base] = splitTemplate(othertype) otherTypeCollection = this._instantiationsOf[base] } for (const possibility of otherTypeCollection) { @@ -873,43 +941,7 @@ export default class PocomathInstance { } for (const instType of instantiationSet) { - if (!(instType in this.Types)) continue - if (this.Types[instType] === anySpec) continue - if (instType in behavior.hasInstantiations) continue - const signature = - substituteInSignature(rawSignature, theTemplateParam, instType) - /* Don't override an explicit implementation: */ - if (signature in imps) continue - /* Don't go too deep */ - let maxdepth = 0 - for (const argType in typeListOfSignature(signature)) { - const depth = argType.split('<').length - if (depth > maxdepth) maxdepth = depth - } - if (maxdepth > this._maxDepthSeen + 1) continue - /* All right, go ahead and instantiate */ - const uses = new Set() - for (const dep of behavior.uses) { - if (this._templateParam(dep)) continue - uses.add(substituteInSignature(dep, theTemplateParam, instType)) - } - const patch = (refs) => { - const innerRefs = {} - for (const dep of behavior.uses) { - if (this._templateParam(dep)) { - innerRefs[dep] = instType - } else { - const outerName = substituteInSignature( - dep, theTemplateParam, instType) - innerRefs[dep] = refs[outerName] - } - } - return behavior.does(innerRefs) - } - this._addTFimplementation(tf_imps, signature, {uses, does: patch}) - tf_imps[signature]._pocoSignature = rawSignature - tf_imps[signature]._pocoInstance = instType - behavior.hasInstantiations[instType] = signature + this._instantiateTemplateImplementation(name, rawSignature, instType) } /* Now add the catchall signature */ /* (Not needed if if it's a bounded template) */ @@ -1031,7 +1063,7 @@ export default class PocomathInstance { for (const possibility of returnType.split('|')) { const instantiated = self._maybeInstantiate(possibility) if (instantiated) { - const tempBase = instantiated.split('<',1)[0] + const [tempBase] = splitTemplate(instantiated) self._invalidateDependents(':' + tempBase) } } @@ -1047,6 +1079,7 @@ export default class PocomathInstance { } Object.defineProperty( patchFunc, 'name', {value: `${name}(${signature})`}) + patchFunc._pocoSignature = rawSignature return patchFunc } Object.defineProperty( @@ -1083,12 +1116,12 @@ export default class PocomathInstance { let tf if (Object.keys(tf_imps).length > 0) { tf = this._typed(name, tf_imps) - Object.defineProperty(tf, 'fromInstance', {value: this}) + tf.fromInstance = this } let metaTF if (Object.keys(meta_imps).length > 0) { metaTF = this._metaTyped(name, meta_imps) - Object.defineProperty(metaTF, 'fromInstance', {value: this}) + metaTF.fromInstance = this } this._meta[name] = metaTF @@ -1137,11 +1170,58 @@ export default class PocomathInstance { return undefined // no such type } // it's a template type, turn it into a template and an arg - let base = type.split('<',1)[0] - const arg = type.slice(base.length+1, -1) + let [base, arg] = splitTemplate(type) return this.instantiateTemplate(base, arg) } + /* Generate and include a template instantiation for operation name + * for the template signature templateSignature instantiated for + * instanceType, returning the resulting implementation. + */ + _instantiateTemplateImplementation(name, templateSignature, instanceType) { + if (!(instanceType in this.Types)) return undefined + if (this.Types[instanceType] === anySpec) return undefined + const imps = this._imps[name] + const behavior = imps[templateSignature] + if (instanceType in behavior.hasInstantiations) return undefined + const signature = substituteInSignature( + templateSignature, theTemplateParam, instanceType) + /* Don't override an explicit implementation: */ + if (signature in imps) return undefined + /* Don't go too deep */ + let maxdepth = 0 + for (const argType in typeListOfSignature(signature)) { + const depth = argType.split('<').length + if (depth > maxdepth) maxdepth = depth + } + if (maxdepth > this._maxDepthSeen + 1) return undefined + /* All right, go ahead and instantiate */ + const uses = new Set() + for (const dep of behavior.uses) { + if (this._templateParam(dep)) continue + uses.add(substituteInSignature(dep, theTemplateParam, instanceType)) + } + const patch = (refs) => { + const innerRefs = {} + for (const dep of behavior.uses) { + if (this._templateParam(dep)) { + innerRefs[dep] = instanceType + } else { + const outerName = substituteInSignature( + dep, theTemplateParam, instanceType) + innerRefs[dep] = refs[outerName] + } + } + return behavior.does(innerRefs) + } + const tf_imps = this._TFimps[name] + this._addTFimplementation(tf_imps, signature, {uses, does: patch}) + tf_imps[signature]._pocoSignature = templateSignature + tf_imps[signature]._pocoInstance = instanceType + behavior.hasInstantiations[instanceType] = signature + return tf_imps[signature] + } + /* Adapts Pocomath-style behavior specification (uses, does) for signature * to typed-function implementations and inserts the result into plain * object imps @@ -1254,6 +1334,7 @@ export default class PocomathInstance { refs.self = self const implementation = does(refs) Object.defineProperty(implementation, 'name', {value: does.name}) + implementation.fromInstance = this // What are we going to do with the return type info in here? return implementation }) @@ -1271,11 +1352,13 @@ export default class PocomathInstance { deferred: true, builtRefs: refs, sigDoes: does, + fromInstance: this, psr: part_self_references } return } const implementation = does(refs) + implementation.fromInstance = this // could do something with return type information here? imps[signature] = implementation } @@ -1310,7 +1393,22 @@ export default class PocomathInstance { name, this._imps[name], neededSig) if (foundSig) { const match = this._pocoFindSignature(name, neededSig) - refs[`self(${neededSig})`] = match.implementation + const neededTemplate = match.fn._pocoSignature + const neededInstance = whichSigInstance( + neededSig, neededTemplate) + const neededImplementation = + this._instantiateTemplateImplementation( + name, neededTemplate, neededInstance) + if (!neededImplementation) { + refs[`self(${neededSig})`] = match.implementation + } else { + if (typeof neededImplementation === 'function') { + refs[`self(${neededSig})`] = neededImplementation + } else { + corrected_self_references.push(neededSig) + remaining_self_references.push(neededSig) + } + } } else { throw new Error( 'Implement inexact self-reference in typed-function for ' @@ -1335,6 +1433,7 @@ export default class PocomathInstance { } imps[aSignature]._pocoSignature = deferral._pocoSignature imps[aSignature]._pocoInstance = deferral._pocoInstance + imps[aSignature].fromInstance = deferral.fromInstance } } @@ -1343,8 +1442,7 @@ export default class PocomathInstance { * in the instance. */ _ensureTemplateTypes(template, type) { - const base = template.split('<', 1)[0] - const arg = template.slice(base.length + 1, -1) + const [base, arg] = splitTemplate(template) if (!arg) { throw new Error( 'Implementation error in _ensureTemplateTypes', template, type) @@ -1481,7 +1579,7 @@ export default class PocomathInstance { if (otherType === 'any') continue if (myType === otherType) continue if (otherType in this.Templates) { - const myBase = myType.split('<',1)[0] + const [myBase] = splitTemplate(myType) if (myBase === otherType) continue if (this.instantiateTemplate(otherType, myType)) { let dummy @@ -1525,10 +1623,24 @@ export default class PocomathInstance { let allMatched = true const implTypes = typeListOfSignature(implSig) for (let i = 0; i < wantTypes.length; ++i) { - if (wantTypes[i] == implTypes[i] - || this.isSubtypeOf(wantTypes[i], implTypes[i])) continue - allMatched = false - break + const implIndex = Math.min(i, implTypes.length - 1) + let implType = implTypes[implIndex] + if (implIndex < i) { + if (implType.slice(0,3) !== '...') { + // ran out of arguments in impl + allMatched = false + break + } + } + if (implType.slice(0,3) === '...') { + implType = implType.slice(3) + } + const hasMatch = implType.split('|').some( + t => (wantTypes[i] === t || this.isSubtypeOf(wantTypes[i], t))) + if (!hasMatch) { + allMatched = false + break + } } if (allMatched) return details } diff --git a/src/tuple/Types/Tuple.mjs b/src/tuple/Types/Tuple.mjs index 49255f7..4ff6685 100644 --- a/src/tuple/Types/Tuple.mjs +++ b/src/tuple/Types/Tuple.mjs @@ -1,5 +1,6 @@ /* A template type representing a homeogeneous tuple of elements */ import PocomathInstance from '../../core/PocomathInstance.mjs' +import {Returns, returnTypeOf} from '../../core/Returns.mjs' const Tuple = new PocomathInstance('Tuple') @@ -33,50 +34,66 @@ Tuple.promoteUnary = { '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. + }) => { + const compType = me.fromInstance.joinTypes( + returnTypeOf(me).split('|'), 'convert') + return Returns( + `Tuple<${compType}>`, t => tuple(...(t.elts.map(x => me(x))))) + } } Tuple.promoteBinaryUnary = { - 'Tuple,Tuple': ({'self(T,T)': meB, 'self(T)': meU, tuple}) => (s,t) => { - let i = -1 - let result = [] - while (true) { - i += 1 - if (i < s.elts.length) { - if (i < t.elts.length) result.push(meB(s.elts[i], t.elts[i])) - else result.push(meU(s.elts[i])) - continue + 'Tuple,Tuple': ({'self(T,T)': meB, 'self(T)': meU, tuple}) => { + const compTypes = returnTypeOf(meB).split('|').concat( + returnTypeOf(meU).split('|')) + const compType = meU.fromInstance.joinTypes(compTypes, 'convert') + return Returns(`Tuple<${compType}>`, (s,t) => { + let i = -1 + let result = [] + while (true) { + i += 1 + if (i < s.elts.length) { + if (i < t.elts.length) result.push(meB(s.elts[i], t.elts[i])) + else result.push(meU(s.elts[i])) + continue + } + if (i < t.elts.length) result.push(meU(t.elts[i])) + else break } - if (i < t.elts.length) result.push(meU(t.elts[i])) - else break - } - return tuple(...result) + return tuple(...result) + }) } } Tuple.promoteBinary = { - 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => (s,t) => { - const lim = Math.max(s.elts.length, t.elts.length) - const result = [] - for (let i = 0; i < lim; ++i) { - result.push(meB(s.elts[i], t.elts[i])) - } - return tuple(...result) + 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => { + const compType = meB.fromInstance.joinTypes( + returnTypeOf(meB).split('|')) + return Returns(`Tuple<${compType}>`, (s,t) => { + const lim = Math.max(s.elts.length, t.elts.length) + const result = [] + for (let i = 0; i < lim; ++i) { + result.push(meB(s.elts[i], t.elts[i])) + } + return tuple(...result) + }) } } Tuple.promoteBinaryStrict = { - 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => (s,t) => { - if (s.elts.length !== t.elts.length) { - throw new RangeError('Tuple length mismatch') // get name of self ?? - } - const result = [] - for (let i = 0; i < s.elts.length; ++i) { - result.push(meB(s.elts[i], t.elts[i])) - } - return tuple(...result) + 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => { + const compType = meB.fromInstance.joinTypes( + returnTypeOf(meB).split('|')) + return Returns(`Tuple<${compType}>`, (s,t) => { + if (s.elts.length !== t.elts.length) { + throw new RangeError('Tuple length mismatch') // get name of self ?? + } + const result = [] + for (let i = 0; i < s.elts.length; ++i) { + result.push(meB(s.elts[i], t.elts[i])) + } + return tuple(...result) + }) } } diff --git a/src/tuple/equalTT.mjs b/src/tuple/equalTT.mjs index 1606410..557ee2d 100644 --- a/src/tuple/equalTT.mjs +++ b/src/tuple/equalTT.mjs @@ -1,11 +1,16 @@ +import Returns from '../core/Returns.mjs' + export * from './Types/Tuple.mjs' export const equalTT = { - 'Tuple,Tuple': ({'self(T,T)': me, 'length(Tuple)': len}) => (s,t) => { + 'Tuple,Tuple': ({ + 'self(T,T)': me, + 'length(Tuple)': len + }) => Returns('boolean', (s,t) => { if (len(s) !== len(t)) return false for (let i = 0; i < len(s); ++i) { if (!me(s.elts[i], t.elts[i])) return false } return true - } + }) } diff --git a/src/tuple/isZero.mjs b/src/tuple/isZero.mjs index 9375277..a48e92a 100644 --- a/src/tuple/isZero.mjs +++ b/src/tuple/isZero.mjs @@ -1,7 +1,10 @@ +import Returns from '../core/Returns.mjs' + export {Tuple} from './Types/Tuple.mjs' export const isZero = { - 'Tuple': ({'self(T)': me}) => t => t.elts.every(e => me(e)) + 'Tuple': ({'self(T)': me}) => Returns( + 'boolean', t => t.elts.every(e => me(e))) // Note we can't just say `every(me)` above since every invokes its // callback with more arguments, which then violates typed-function's // signature for `me` diff --git a/src/tuple/tuple.mjs b/src/tuple/tuple.mjs index 893b54d..9cd0c65 100644 --- a/src/tuple/tuple.mjs +++ b/src/tuple/tuple.mjs @@ -1,6 +1,10 @@ +import Returns from '../core/Returns.mjs' + export {Tuple} from './Types/Tuple.mjs' /* The purpose of the template argument is to ensure that all of the args * are convertible to the same type. */ -export const tuple = {'...T': () => args => ({elts: args})} +export const tuple = { + '...T': ({T}) => Returns(`Tuple<${T}>`, args => ({elts: args})) +} diff --git a/test/tuple/_native.mjs b/test/tuple/_native.mjs index a37b213..8a9b34c 100644 --- a/test/tuple/_native.mjs +++ b/test/tuple/_native.mjs @@ -54,6 +54,9 @@ describe('tuple', () => { assert.deepStrictEqual( math.subtract(math.tuple(3n,4n,5n), math.tuple(2n,1n,0n)), math.tuple(1n,3n,5n)) + assert.deepStrictEqual( + math.returnTypeOf('subtract', 'Tuple,Tuple'), + 'Tuple') assert.throws( () => math.subtract(math.tuple(5,6), math.tuple(7)), /RangeError/) @@ -104,9 +107,16 @@ describe('tuple', () => { }) it('supports sqrt', () => { + const mixedTuple = math.tuple(2, math.complex(0,2), 1.5) assert.deepStrictEqual( - math.sqrt(math.tuple(4,-4,2.25)), - math.tuple(2, math.complex(0,2), 1.5)) + mixedTuple, + math.tuple(math.complex(2), math.complex(0,2), math.complex(1.5))) + assert.strictEqual( + math.returnTypeOf('tuple', 'NumInt, Complex, number'), + 'Tuple>') + assert.deepStrictEqual(math.sqrt(math.tuple(4,-4,2.25)), mixedTuple) + assert.strictEqual( + math.returnTypeOf('sqrt', 'Tuple'), 'Tuple>') }) })