refactor: Convert resolution to two-tier system
Typed-function's sort order/matching algorithm was interfering with template resolution. This commit solves the difficulty by moving the "catchall" implementations that implement generation of new template instances into a separate "fallback" typed-function universe, so that Pocomath can control exactly when that is searched. Removes a couple of the matching anomalies already noted in the tests. Also extends return types to somewhat more functions.
This commit is contained in:
parent
a2f76a55b8
commit
de42c22ab4
@ -2,15 +2,13 @@ import {Returns, returnTypeOf} from '../../core/Returns.mjs'
|
||||
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||
|
||||
const Complex = new PocomathInstance('Complex')
|
||||
// Base type that should generally not be used directly
|
||||
Complex.installType('Complex', {
|
||||
test: z => z && typeof z === 'object' && 're' in z && 'im' in z
|
||||
})
|
||||
// Now the template type: Complex numbers are actually always homogeneous
|
||||
// in their component types.
|
||||
// in their component types. For an explanation of the meanings of the
|
||||
// properties, see ../../tuple/Types/Tuple.mjs
|
||||
Complex.installType('Complex<T>', {
|
||||
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
|
||||
base: z => z && typeof z === 'object' && 're' in z && 'im' in z,
|
||||
test: testT => z => testT(z.re) && testT(z.im),
|
||||
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
|
||||
from: {
|
||||
T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T)
|
||||
U: convert => u => {
|
||||
|
@ -3,6 +3,7 @@ export * from './Types/Complex.mjs'
|
||||
|
||||
export const abs = {
|
||||
'Complex<T>': ({
|
||||
T,
|
||||
sqrt, // Unfortunately no notation yet for the needed signature
|
||||
'absquare(T)': baseabsq,
|
||||
'absquare(Complex<T>)': absq
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Returns from '../core/Returns.mjs'
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
/* Returns true if w is z multiplied by a complex unit */
|
||||
@ -9,9 +10,9 @@ export const associate = {
|
||||
'one(T)': uno,
|
||||
'complex(T,T)': cplx,
|
||||
'negate(Complex<T>)': neg
|
||||
}) => (w,z) => {
|
||||
}) => Returns('boolean', (w,z) => {
|
||||
if (eq(w,z) || eq(w,neg(z))) return true
|
||||
const ti = times(z, cplx(zr(z.re), uno(z.im)))
|
||||
return eq(w,ti) || eq(w,neg(ti))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -6,13 +6,15 @@ export const multiply = {
|
||||
T,
|
||||
'complex(T,T)': cplx,
|
||||
'add(T,T)': plus,
|
||||
'subtract(T,T)': sub,
|
||||
'subtract(T,T)': subt,
|
||||
'self(T,T)': me,
|
||||
'conjugate(T)': conj // makes quaternion multiplication work
|
||||
}) => Returns(
|
||||
`Complex<${T}>`,
|
||||
(w,z) => cplx(
|
||||
sub(me(w.re, z.re), me(conj(w.im), z.im)),
|
||||
plus(me(conj(w.re), z.im), me(w.im, z.re)))
|
||||
(w,z) => {
|
||||
const realpart = subt(me( w.re, z.re), me(conj(w.im), z.im))
|
||||
const imagpart = plus(me(conj(w.re), z.im), me( w.im, z.re))
|
||||
return cplx(realpart, imagpart)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ const anySpec = {} // fixed dummy specification of 'any' type
|
||||
|
||||
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 a new signature just like sig but with the parameter replaced by
|
||||
@ -21,6 +22,8 @@ function substituteInSignature(signature, parameter, type) {
|
||||
return sig.replaceAll(pattern, type)
|
||||
}
|
||||
|
||||
let lastWhatToDo = null // used in an infinite descent check
|
||||
|
||||
export default class PocomathInstance {
|
||||
/* Disallowed names for ops; beware, this is slightly non-DRY
|
||||
* in that if a new top-level PocomathInstance method is added, its name
|
||||
@ -55,24 +58,39 @@ export default class PocomathInstance {
|
||||
this._affects = {}
|
||||
this._typed = typed.create()
|
||||
this._typed.clear()
|
||||
this._typed.addTypes([{name: 'ground', test: () => true}])
|
||||
// The following is an additional typed-function universe for resolving
|
||||
// uninstantiated template instances. It is linked to the main one in
|
||||
// its onMismatch function, below:
|
||||
this._metaTyped = typed.create()
|
||||
this._metaTyped.clear()
|
||||
// And these are the meta bindings: (I think we don't need separate
|
||||
// invalidation for them as they are only accessed through a main call.)
|
||||
this._meta = {} // The resulting typed-functions
|
||||
this._metaTFimps = {} // and their implementations
|
||||
const me = this
|
||||
const myTyped = this._typed
|
||||
this._typed.onMismatch = (name, args, sigs) => {
|
||||
if (me._invalid.has(name)) {
|
||||
return me[name](...args) // rebuild implementation and try again
|
||||
// rebuild implementation and try again
|
||||
return me[name](...args)
|
||||
}
|
||||
myTyped.throwMismatchError(name, args, sigs)
|
||||
const metaversion = me._meta[name]
|
||||
if (metaversion) {
|
||||
return me._meta[name](...args)
|
||||
}
|
||||
/* List of types installed in the instance. We start with just dummies
|
||||
* for the 'any' type and for type parameters:
|
||||
*/
|
||||
me._typed.throwMismatchError(name, args, sigs)
|
||||
}
|
||||
// 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.Types.ground = anySpec
|
||||
// All the template types that have been defined
|
||||
// Types that have been moved into the metaverse:
|
||||
this._metafiedTypes = new Set()
|
||||
// All the template types that have been defined:
|
||||
this.Templates = {}
|
||||
// The actual type testing functions
|
||||
// And their instantiations:
|
||||
this._instantiationsOf = {}
|
||||
// The actual type testing functions:
|
||||
this._typeTests = {}
|
||||
// For each type, gives all of its (in)direct subtypes in topo order:
|
||||
this._subtypes = {}
|
||||
@ -87,27 +105,19 @@ export default class PocomathInstance {
|
||||
this._maxDepthSeen = 1 // deepest template nesting we've actually encountered
|
||||
this._invalid = new Set() // methods that are currently invalid
|
||||
this._config = {predictable: false, epsilon: 1e-12}
|
||||
const self = this
|
||||
this.config = new Proxy(this._config, {
|
||||
get: (target, property) => target[property],
|
||||
set: (target, property, value) => {
|
||||
if (value !== target[property]) {
|
||||
target[property] = value
|
||||
self._invalidateDependents('config')
|
||||
me._invalidateDependents('config')
|
||||
}
|
||||
return true // successful
|
||||
}
|
||||
})
|
||||
this._plainFunctions = new Set() // the names of the plain functions
|
||||
this._chainRepository = {} // place to store chainified functions
|
||||
|
||||
this._installFunctions({
|
||||
typeOf: {
|
||||
ground: {uses: new Set(), does: () => Returns('string', () => 'any')}
|
||||
}
|
||||
})
|
||||
|
||||
this.joinTypes = this.joinTypes.bind(this)
|
||||
this.joinTypes = this.joinTypes.bind(me)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -330,9 +340,10 @@ export default class PocomathInstance {
|
||||
`Type name '${type}' reserved for template parameter`)
|
||||
}
|
||||
if (parts.some(this._templateParam.bind(this))) {
|
||||
// It's a template, deal with it separately
|
||||
// It's an uninstantiated template, deal with it separately
|
||||
return this._installTemplateType(type, spec)
|
||||
}
|
||||
const base = parts[0]
|
||||
if (type in this.Types) {
|
||||
if (spec !== this.Types[type]) {
|
||||
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
||||
@ -345,7 +356,7 @@ export default class PocomathInstance {
|
||||
}
|
||||
let beforeType = spec.refines
|
||||
if (!beforeType) {
|
||||
beforeType = 'ground'
|
||||
beforeType = 'any'
|
||||
for (const other of spec.before || []) {
|
||||
if (other in this.Types) {
|
||||
beforeType = other
|
||||
@ -387,6 +398,21 @@ export default class PocomathInstance {
|
||||
for (const subtype of this._subtypes[from]) {
|
||||
this._priorTypes[nextSuper].add(subtype)
|
||||
}
|
||||
|
||||
/* 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]) {
|
||||
this._metafy(from)
|
||||
try {
|
||||
this._metaTyped.addConversion(
|
||||
{from, to: toParts[0], convert: spec.from[from]})
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextSuper = this.Types[nextSuper].refines
|
||||
}
|
||||
}
|
||||
@ -411,6 +437,16 @@ export default class PocomathInstance {
|
||||
convert: this.Types[to].from[fromtype]
|
||||
})
|
||||
this._invalidateDependents(':' + nextSuper)
|
||||
/* Add the conversion in the metaverse if need be: */
|
||||
const toParts = nextSuper.split('<', 2)
|
||||
if (toParts.length > 1 && base !== toParts[0]) {
|
||||
this._metafy(type)
|
||||
this._metaTyped.addConversion({
|
||||
from: type,
|
||||
to: toParts[0],
|
||||
convert: this.Types[to].from[fromtype]
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
this._priorTypes[nextSuper].add(type)
|
||||
@ -425,6 +461,12 @@ export default class PocomathInstance {
|
||||
this._installFunctions({typeOf: imp})
|
||||
})
|
||||
|
||||
_metafy(type) {
|
||||
if (this._metafiedTypes.has(type)) return
|
||||
this._metaTyped.addTypes([{name: type, test: this._typeTests[type]}])
|
||||
this._metafiedTypes.add(type)
|
||||
}
|
||||
|
||||
_addSubtypeTo(sup, sub) {
|
||||
if (this.isSubtypeOf(sub, sup)) return
|
||||
const supSubs = this._subtypes[sup]
|
||||
@ -435,8 +477,10 @@ export default class PocomathInstance {
|
||||
supSubs.splice(i, 0, sub)
|
||||
}
|
||||
|
||||
/* Returns true if typeA is a subtype of type B */
|
||||
/* Returns true if typeA is a strict subtype of type B */
|
||||
isSubtypeOf = Returns('boolean', function(typeA, typeB) {
|
||||
// Currently not considering types to be a subtype of 'any'
|
||||
if (typeB === 'any' || typeA === 'any') return false
|
||||
return this._subtypes[typeB].includes(typeA)
|
||||
})
|
||||
|
||||
@ -483,7 +527,6 @@ export default class PocomathInstance {
|
||||
if (!typeA) return typeB
|
||||
if (!typeB) return typeA
|
||||
if (typeA === 'any' || typeB === 'any') return 'any'
|
||||
if (typeA === 'ground' || typeB === 'ground') return 'ground'
|
||||
if (typeA === typeB) return typeA
|
||||
const subber = convert ? this._priorTypes : this._subtypes
|
||||
const pick = convert ? 'has' : 'includes'
|
||||
@ -510,7 +553,8 @@ export default class PocomathInstance {
|
||||
* signatures of operations, but which have not actually been installed:
|
||||
*/
|
||||
undefinedTypes = Returns('Array<string>', function() {
|
||||
return Array.from(this._seenTypes).filter(t => !(t in this.Types))
|
||||
return Array.from(this._seenTypes).filter(
|
||||
t => !(t in this.Types || t in this.Templates))
|
||||
})
|
||||
|
||||
/* Used internally to install a template type */
|
||||
@ -526,6 +570,18 @@ export default class PocomathInstance {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// install the "base type" in the meta universe:
|
||||
let beforeType = 'any'
|
||||
for (const other of spec.before || []) {
|
||||
if (other in this.templates) {
|
||||
beforeType = other
|
||||
break
|
||||
}
|
||||
}
|
||||
this._metaTyped.addTypes([{name: base, test: spec.base}], beforeType)
|
||||
this._instantiationsOf[base] = new Set()
|
||||
|
||||
// update the typeOf function
|
||||
const imp = {}
|
||||
imp[type] = {
|
||||
@ -534,6 +590,9 @@ export default class PocomathInstance {
|
||||
}
|
||||
this._installFunctions({typeOf: imp})
|
||||
|
||||
// Invalidate any functions that reference this template type:
|
||||
this._invalidateDependents(':' + base)
|
||||
|
||||
// Nothing else actually happens until we match a template parameter
|
||||
this.Templates[base] = {type, spec}
|
||||
}
|
||||
@ -578,12 +637,11 @@ export default class PocomathInstance {
|
||||
}
|
||||
opImps[signature] = {
|
||||
explicit,
|
||||
resolved: false,
|
||||
uses: behavior.uses,
|
||||
does: behavior.does
|
||||
}
|
||||
if (explicit) {
|
||||
opImps[signature].resolved = false
|
||||
} else {
|
||||
if (!explicit) {
|
||||
opImps[signature].hasInstantiations = {}
|
||||
opImps[signature].needsInstantiations = new Set()
|
||||
}
|
||||
@ -628,6 +686,8 @@ export default class PocomathInstance {
|
||||
_invalidate(name, reason) {
|
||||
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:
|
||||
@ -692,10 +752,8 @@ export default class PocomathInstance {
|
||||
if (!imps) {
|
||||
throw new SyntaxError(`No implementations for ${name}`)
|
||||
}
|
||||
if (!(this._TFimps[name])) {
|
||||
this._TFimps[name] = {}
|
||||
}
|
||||
const tf_imps = this._TFimps[name]
|
||||
const meta_imps = this._metaTFimps[name]
|
||||
/* Collect the entries we know the types for */
|
||||
const usableEntries = []
|
||||
for (const entry of Object.entries(imps)) {
|
||||
@ -751,6 +809,79 @@ export default class PocomathInstance {
|
||||
}
|
||||
}
|
||||
|
||||
/* Prevent other existing signatures from blocking use of top-level
|
||||
* templates via conversions:
|
||||
*/
|
||||
let baseSignature = rawSignature.replaceAll(templateCall, '')
|
||||
/* Any remaining template params are top-level */
|
||||
const signature = substituteInSignature(
|
||||
baseSignature, theTemplateParam, 'any')
|
||||
const hasTopLevel = (signature !== baseSignature)
|
||||
if (!ubType && hasTopLevel) {
|
||||
// collect upper-bound types
|
||||
const ubTypes = new Set()
|
||||
for (const othersig in imps) {
|
||||
const thisUB = upperBounds.exec(othersig)
|
||||
if (thisUB) ubTypes.add(thisUB[2])
|
||||
let basesig = othersig.replaceAll(templateCall, '')
|
||||
if (basesig !== othersig) {
|
||||
// A template
|
||||
const testsig = substituteInSignature(
|
||||
basesig, theTemplateParam, '')
|
||||
if (testsig === basesig) {
|
||||
// that is not also top-level
|
||||
for (const templateType of typeListOfSignature(basesig)) {
|
||||
if (templateType.slice(0,3) === '...') {
|
||||
templateType = templateType.slice(3)
|
||||
}
|
||||
ubTypes.add(templateType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const othersig in imps) {
|
||||
let basesig = othersig.replaceAll(templateCall, '')
|
||||
const testsig = substituteInSignature(
|
||||
basesig, theTemplateParam, '')
|
||||
if (testsig !== basesig) continue // a top-level template
|
||||
for (let othertype of typeListOfSignature(othersig)) {
|
||||
if (othertype.slice(0,3) === '...') {
|
||||
othertype = othertype.slice(3)
|
||||
}
|
||||
if (this.Types[othertype] === anySpec) continue
|
||||
const testType = substituteInSignature(
|
||||
othertype, theTemplateParam, '')
|
||||
let otherTypeCollection = [othertype]
|
||||
if (testType !== othertype) {
|
||||
const base = othertype.split('<',1)[0]
|
||||
otherTypeCollection = this._instantiationsOf[base]
|
||||
}
|
||||
for (const possibility of otherTypeCollection) {
|
||||
for (const convtype of this._priorTypes[possibility]) {
|
||||
if (this.isSubtypeOf(convtype, possibility)) continue
|
||||
if (ubTypes.has(convtype)) continue
|
||||
let belowUB = false
|
||||
for (const anUB of ubTypes) {
|
||||
if (anUB in this.Templates) {
|
||||
if (convtype.slice(0, anUB.length) === anUB) {
|
||||
belowUB = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if (this.isSubtypeOf(convtype, anUB)) {
|
||||
belowUB = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (belowUB) continue
|
||||
instantiationSet.add(convtype)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const instType of instantiationSet) {
|
||||
if (!(instType in this.Types)) continue
|
||||
if (this.Types[instType] === anySpec) continue
|
||||
@ -785,8 +916,7 @@ export default class PocomathInstance {
|
||||
}
|
||||
return behavior.does(innerRefs)
|
||||
}
|
||||
this._addTFimplementation(
|
||||
tf_imps, signature, {uses, does: patch})
|
||||
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
||||
tf_imps[signature]._pocoSignature = rawSignature
|
||||
tf_imps[signature]._pocoInstance = instType
|
||||
behavior.hasInstantiations[instType] = signature
|
||||
@ -794,13 +924,7 @@ export default class PocomathInstance {
|
||||
/* Now add the catchall signature */
|
||||
/* (Not needed if if it's a bounded template) */
|
||||
if (ubType) continue
|
||||
if ('_catchall_' in behavior.hasInstantiations) continue
|
||||
let templateCall = `<${theTemplateParam}>`
|
||||
/* Relying here that the base of 'Foo<T>' is 'Foo': */
|
||||
let baseSignature = rawSignature.replaceAll(templateCall, '')
|
||||
/* Any remaining template params are top-level */
|
||||
const signature = substituteInSignature(
|
||||
baseSignature, theTemplateParam, 'ground')
|
||||
if (behavior.resolved) continue
|
||||
/* The catchall signature has to detect the actual type of the call
|
||||
* and add the new instantiations.
|
||||
* First, prepare the type inference data:
|
||||
@ -840,8 +964,10 @@ export default class PocomathInstance {
|
||||
/* For return type annotation, we may have to fix this to
|
||||
propagate the return type. At the moment we are just bagging
|
||||
*/
|
||||
const patch = (refs) => (...args) => {
|
||||
const patch = () => {
|
||||
const patchFunc = (...tfBundledArgs) => {
|
||||
/* We unbundle the rest arg if there is one */
|
||||
let args = Array.from(tfBundledArgs)
|
||||
const regLength = args.length - 1
|
||||
if (restParam) {
|
||||
const restArgs = args.pop()
|
||||
@ -864,11 +990,12 @@ export default class PocomathInstance {
|
||||
}
|
||||
if (argType === 'any') {
|
||||
throw TypeError(
|
||||
`In call to ${name}, incompatible template arguments: `
|
||||
`In call to ${name}, `
|
||||
+ 'incompatible template arguments:'
|
||||
// + args.map(a => JSON.stringify(a)).join(', ')
|
||||
// unfortunately barfs on bigints. Need a better formatter
|
||||
// wish we could just use the one that console.log uses;
|
||||
// is that accessible somehow?
|
||||
// unfortunately barfs on bigints. Need a better
|
||||
// formatter. I wish we could just use the one that
|
||||
// console.log uses; is that accessible somehow?
|
||||
+ args.map(a => a.toString()).join(', ')
|
||||
+ ' of types ' + argTypes.join(', ') + argType)
|
||||
}
|
||||
@ -900,9 +1027,10 @@ export default class PocomathInstance {
|
||||
type, theTemplateParam, instantiateFor))
|
||||
const wantSig = wantTypes.join(',')
|
||||
/* Now we have to add any actual types that are relevant
|
||||
* to this invocation. Namely, that would be every formal parameter
|
||||
* type in the invocation, with the parameter template instantiated
|
||||
* by instantiateFor, and for all of instantiateFor's "prior types"
|
||||
* to this invocation. Namely, that would be every formal
|
||||
* parameter type in the invocation, with the parameter
|
||||
* template instantiated by instantiateFor, and for all of
|
||||
* instantiateFor's "prior types"
|
||||
*/
|
||||
for (j = 0; j < parTypes.length; ++j) {
|
||||
if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) {
|
||||
@ -910,61 +1038,21 @@ export default class PocomathInstance {
|
||||
self._ensureTemplateTypes(parTypes[j], instantiateFor)
|
||||
}
|
||||
}
|
||||
/* Transform the arguments if we used any conversions: */
|
||||
if (usedConversions) {
|
||||
i = - 1
|
||||
for (j = 0; j < args.length; ++j) {
|
||||
if (i < parTypes.length - 1) ++i
|
||||
let wantType = parTypes[i]
|
||||
if (wantType.slice(0,3) === '...') {
|
||||
wantType = wantType.slice(3)
|
||||
}
|
||||
wantType = substituteInSignature(
|
||||
wantType, theTemplateParam, instantiateFor)
|
||||
if (wantType !== parTypes[i]) {
|
||||
args[j] = self._typed.convert(args[j], wantType)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Finally reassemble the rest args if there were any */
|
||||
if (restParam) {
|
||||
const restArgs = args.slice(regLength)
|
||||
args = args.slice(0,regLength)
|
||||
args.push(restArgs)
|
||||
}
|
||||
/* Arrange that the desired instantiation will be there next
|
||||
* time so we don't have to go through that again for this type
|
||||
*/
|
||||
|
||||
/* Request the desired instantiation: */
|
||||
// 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)
|
||||
self._invalidate(name)
|
||||
// And update refs because we now know the type we're instantiating
|
||||
// for:
|
||||
refs[theTemplateParam] = instantiateFor
|
||||
const innerRefs = {}
|
||||
for (const dep in simplifiedUses) {
|
||||
const simplifiedDep = simplifiedUses[dep]
|
||||
if (dep === simplifiedDep) {
|
||||
innerRefs[dep] = refs[dep]
|
||||
} else {
|
||||
let [func, needsig] = dep.split(/[()]/)
|
||||
if (self._typed.isTypedFunction(refs[simplifiedDep])) {
|
||||
const subsig = substituteInSignature(
|
||||
needsig, theTemplateParam, instantiateFor)
|
||||
let resname = simplifiedDep
|
||||
if (resname == 'self') resname = name
|
||||
innerRefs[dep] = self.resolve(
|
||||
resname, subsig, refs[simplifiedDep])
|
||||
} else {
|
||||
innerRefs[dep] = refs[simplifiedDep]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally ready to make the call.
|
||||
const implementation = behavior.does(innerRefs)
|
||||
const brandNewMe = self[name]
|
||||
const whatToDo = self._typed.resolve(brandNewMe, args)
|
||||
// We can access return type information here
|
||||
// And in particular, if it might be a template, we should try to
|
||||
// instantiate it:
|
||||
const returnType = returnTypeOf(implementation, wantSig, self)
|
||||
const returnType = returnTypeOf(whatToDo.fn, wantSig, self)
|
||||
for (const possibility of returnType.split('|')) {
|
||||
const instantiated = self._maybeInstantiate(possibility)
|
||||
if (instantiated) {
|
||||
@ -972,18 +1060,28 @@ export default class PocomathInstance {
|
||||
self._invalidateDependents(':' + tempBase)
|
||||
}
|
||||
}
|
||||
return implementation(...args)
|
||||
if (whatToDo === lastWhatToDo) {
|
||||
throw new Error(
|
||||
`Infinite recursion in resolving $name called on`
|
||||
+ args.map(x => x.toString()).join(','))
|
||||
}
|
||||
Object.defineProperty(patch, 'name', {value: `${name}(${signature})`})
|
||||
// TODO: Decorate patch with a function that calculates the
|
||||
lastWhatToDo = whatToDo
|
||||
const retval = whatToDo.implementation(...args)
|
||||
lastWhatToDo = null
|
||||
return retval
|
||||
}
|
||||
Object.defineProperty(
|
||||
patchFunc, 'name', {value: `${name}(${signature})`})
|
||||
return patchFunc
|
||||
}
|
||||
Object.defineProperty(
|
||||
patch, 'name', {value: `generate[${name}(${signature})]`})
|
||||
// TODO?: Decorate patch with a function that calculates the
|
||||
// correct return type a priori. Deferring because unclear what
|
||||
// aspects will be merged into typed-function.
|
||||
//
|
||||
// The actual uses value needs to be a set:
|
||||
const outerUses = new Set(Object.values(simplifiedUses))
|
||||
this._addTFimplementation(
|
||||
tf_imps, signature, {uses: outerUses, does: patch})
|
||||
behavior.hasInstantiations._catchall_ = rawSignature
|
||||
meta_imps, signature, {uses: new Set(), does: patch})
|
||||
behavior.resolved = true
|
||||
}
|
||||
this._correctPartialSelfRefs(name, tf_imps)
|
||||
// Make sure we have all of the needed (template) types; and if they
|
||||
@ -1007,8 +1105,19 @@ export default class PocomathInstance {
|
||||
delete fromBehavior.hasInstantiations[imp._pocoInstance]
|
||||
}
|
||||
}
|
||||
const tf = this._typed(name, tf_imps)
|
||||
let tf
|
||||
if (Object.keys(tf_imps).length > 0) {
|
||||
tf = this._typed(name, tf_imps)
|
||||
Object.defineProperty(tf, 'fromInstance', {value: this})
|
||||
}
|
||||
let metaTF
|
||||
if (Object.keys(meta_imps).length > 0) {
|
||||
metaTF = this._metaTyped(name, meta_imps)
|
||||
Object.defineProperty(metaTF, 'fromInstance', {value: this})
|
||||
}
|
||||
this._meta[name] = metaTF
|
||||
|
||||
tf = tf || metaTF
|
||||
Object.defineProperty(this, name, {configurable: true, value: tf})
|
||||
return tf
|
||||
}
|
||||
@ -1077,7 +1186,10 @@ export default class PocomathInstance {
|
||||
'typed-function does not support mixed full and '
|
||||
+ 'partial self-reference')
|
||||
}
|
||||
if (subsetOfKeys(typesOfSignature(needsig), this.Types)) {
|
||||
const needTypes = typesOfSignature(needsig)
|
||||
const mergedTypes = Object.assign(
|
||||
{}, this.Types, this.Templates)
|
||||
if (subsetOfKeys(needTypes, mergedTypes)) {
|
||||
part_self_references.push(needsig)
|
||||
}
|
||||
}
|
||||
@ -1097,10 +1209,19 @@ export default class PocomathInstance {
|
||||
* accumulating:
|
||||
*/
|
||||
if (needsig) {
|
||||
const tempTF = this._typed('dummy_' + func, this._TFimps[func])
|
||||
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 = this._typed.find(tempTF, needsig, {exact: true})
|
||||
result = typedUniverse.find(tempTF, needsig, {exact: true})
|
||||
} catch {}
|
||||
if (result) {
|
||||
refs[dep] = result
|
||||
@ -1158,7 +1279,7 @@ export default class PocomathInstance {
|
||||
return
|
||||
}
|
||||
const implementation = does(refs)
|
||||
// could do something with return type information here
|
||||
// could do something with return type information here?
|
||||
imps[signature] = implementation
|
||||
}
|
||||
|
||||
@ -1168,38 +1289,53 @@ export default class PocomathInstance {
|
||||
const deferral = imps[aSignature]
|
||||
const part_self_references = deferral.psr
|
||||
const corrected_self_references = []
|
||||
const remaining_self_references = []
|
||||
const refs = deferral.builtRefs
|
||||
for (const neededSig of part_self_references) {
|
||||
// Have to find a match for neededSig among the other signatures
|
||||
// of this function. That's a job for typed-function, but we will
|
||||
// try here:
|
||||
if (neededSig in imps) { // the easy case
|
||||
corrected_self_references.push(neededSig)
|
||||
remaining_self_references.push(neededSig)
|
||||
continue
|
||||
}
|
||||
// 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(name, imps, neededSig)
|
||||
let foundSig = this._findSubtypeImpl(name, imps, neededSig)
|
||||
if (foundSig) {
|
||||
corrected_self_references.push(foundSig)
|
||||
remaining_self_references.push(neededSig)
|
||||
} else {
|
||||
// Maybe it's a template instance we don't yet have
|
||||
foundSig = this._findSubtypeImpl(
|
||||
name, this._imps[name], neededSig)
|
||||
if (foundSig) {
|
||||
const match = this._pocoFindSignature(name, neededSig)
|
||||
refs[`self(${neededSig})`] = match.implementation
|
||||
} else {
|
||||
throw new Error(
|
||||
'Implement inexact self-reference in typed-function for '
|
||||
+ `${name}(${neededSig})`)
|
||||
}
|
||||
}
|
||||
const refs = deferral.builtRefs
|
||||
}
|
||||
const does = deferral.sigDoes
|
||||
if (remaining_self_references.length > 0) {
|
||||
imps[aSignature] = this._typed.referTo(
|
||||
...corrected_self_references, (...impls) => {
|
||||
for (let i = 0; i < part_self_references.length; ++i) {
|
||||
refs[`self(${part_self_references[i]})`] = impls[i]
|
||||
for (let i = 0; i < remaining_self_references.length; ++i) {
|
||||
refs[`self(${remaining_self_references[i]})`] = impls[i]
|
||||
}
|
||||
const implementation = does(refs)
|
||||
// What will we do with the return type info in here?
|
||||
return implementation
|
||||
}
|
||||
)
|
||||
} else {
|
||||
imps[aSignature] = does(refs)
|
||||
}
|
||||
imps[aSignature]._pocoSignature = deferral._pocoSignature
|
||||
imps[aSignature]._pocoInstance = deferral._pocoInstance
|
||||
}
|
||||
@ -1247,7 +1383,7 @@ export default class PocomathInstance {
|
||||
if (wantsType in this.Types) return false
|
||||
// OK, need to generate the type from the template
|
||||
// Set up refines, before, test, and from
|
||||
const newTypeSpec = {refines: base}
|
||||
const newTypeSpec = {}
|
||||
const maybeFrom = {}
|
||||
const template = this.Templates[base].spec
|
||||
if (!template) {
|
||||
@ -1255,6 +1391,11 @@ export default class PocomathInstance {
|
||||
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
|
||||
}
|
||||
const instantiatorSpec = this.Types[instantiator]
|
||||
if (instantiatorSpec.refines) {
|
||||
this.instantiateTemplate(base, instantiatorSpec.refines)
|
||||
// Assuming our templates are covariant, I guess
|
||||
newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>`
|
||||
}
|
||||
let beforeTypes = []
|
||||
if (instantiatorSpec.before) {
|
||||
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
||||
@ -1268,18 +1409,15 @@ export default class PocomathInstance {
|
||||
if (beforeTypes.length > 0) {
|
||||
newTypeSpec.before = beforeTypes
|
||||
}
|
||||
newTypeSpec.test = template.test(this._typeTests[instantiator])
|
||||
const templateTest = template.test(this._typeTests[instantiator])
|
||||
newTypeSpec.test = x => (template.base(x) && templateTest(x))
|
||||
if (template.from) {
|
||||
for (let source in template.from) {
|
||||
const instSource = substituteInSignature(
|
||||
source, theTemplateParam, instantiator)
|
||||
let usesFromParam = false
|
||||
for (const word of instSource.split(/[<>]/)) {
|
||||
if (word === templateFromParam) {
|
||||
usesFromParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
const testSource = substituteInSignature(
|
||||
instSource, templateFromParam, instantiator)
|
||||
const usesFromParam = (testSource !== instSource)
|
||||
if (usesFromParam) {
|
||||
for (const iFrom in instantiatorSpec.from) {
|
||||
const finalSource = substituteInSignature(
|
||||
@ -1287,12 +1425,14 @@ export default class PocomathInstance {
|
||||
maybeFrom[finalSource] = template.from[source](
|
||||
instantiatorSpec.from[iFrom])
|
||||
}
|
||||
if (testSource !== wantsType) { // subtypes handled with refines
|
||||
// Assuming all templates are covariant here, I guess...
|
||||
for (const subType of this._subtypes[instantiator]) {
|
||||
const finalSource = substituteInSignature(
|
||||
instSource, templateFromParam, subType)
|
||||
maybeFrom[finalSource] = template.from[source](x => x)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
maybeFrom[instSource] = template.from[source]
|
||||
}
|
||||
@ -1303,6 +1443,7 @@ export default class PocomathInstance {
|
||||
newTypeSpec.from = maybeFrom
|
||||
}
|
||||
this.installType(wantsType, newTypeSpec)
|
||||
this._instantiationsOf[base].add(wantsType)
|
||||
return wantsType
|
||||
})
|
||||
|
||||
@ -1314,7 +1455,7 @@ export default class PocomathInstance {
|
||||
const otherTypeList = typeListOfSignature(otherSig)
|
||||
if (typeList.length !== otherTypeList.length) continue
|
||||
let allMatch = true
|
||||
let paramBound = 'ground'
|
||||
let paramBound = 'any'
|
||||
for (let k = 0; k < typeList.length; ++k) {
|
||||
let myType = typeList[k]
|
||||
let otherType = otherTypeList[k]
|
||||
@ -1326,8 +1467,7 @@ export default class PocomathInstance {
|
||||
otherTypeList[k] = `...${paramBound}`
|
||||
otherType = paramBound
|
||||
}
|
||||
const adjustedOtherType = otherType.replaceAll(
|
||||
`<${theTemplateParam}>`, '')
|
||||
const adjustedOtherType = otherType.replaceAll(templateCall, '')
|
||||
if (adjustedOtherType !== otherType) {
|
||||
otherTypeList[k] = adjustedOtherType
|
||||
otherType = adjustedOtherType
|
||||
@ -1342,22 +1482,21 @@ export default class PocomathInstance {
|
||||
theTemplateParam, paramBound)
|
||||
}
|
||||
if (otherType === 'any') continue
|
||||
if (otherType === 'ground') continue
|
||||
if (!(otherType in this.Types)) {
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
if (myType === otherType
|
||||
|| this.isSubtypeOf(myType, otherType)) {
|
||||
continue
|
||||
}
|
||||
if (myType === otherType) continue
|
||||
if (otherType in this.Templates) {
|
||||
const myBase = myType.split('<',1)[0]
|
||||
if (myBase === otherType) continue
|
||||
if (this.instantiateTemplate(otherType, myType)) {
|
||||
let dummy
|
||||
dummy = this[name] // for side effects
|
||||
return this._findSubtypeImpl(name, this._imps[name], neededSig)
|
||||
}
|
||||
}
|
||||
if (!(otherType in this.Types)) {
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
if (this.isSubtypeOf(myType, otherType)) continue
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
@ -1373,31 +1512,16 @@ export default class PocomathInstance {
|
||||
if (!this._typed.isTypedFunction(typedFunction)) {
|
||||
typedFunction = this[name]
|
||||
}
|
||||
let result = undefined
|
||||
const haveTF = this._typed.isTypedFunction(typedFunction)
|
||||
if (haveTF) {
|
||||
// First try a direct match
|
||||
let result
|
||||
try {
|
||||
result = this._typed.findSignature(typedFunction, sig, {exact: true})
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
if (result || !(this._imps[name])) return result
|
||||
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
|
||||
if (foundsig) {
|
||||
if (haveTF) {
|
||||
return this._typed.findSignature(typedFunction, foundsig)
|
||||
}
|
||||
// We have an implementation but not a typed function. Do the best
|
||||
// we can:
|
||||
const foundImpl = this._imps[name][foundsig]
|
||||
const needs = {}
|
||||
for (const dep of foundImpl.uses) {
|
||||
const [base, sig] = dep.split('()')
|
||||
needs[dep] = this.resolve(base, sig)
|
||||
}
|
||||
const pseudoImpl = foundImpl.does(needs)
|
||||
return {fn: pseudoImpl, implementation: pseudoImpl}
|
||||
}
|
||||
if (result) return result
|
||||
// Next, look ourselves but with subtypes:
|
||||
const wantTypes = typeListOfSignature(sig)
|
||||
for (const [implSig, details]
|
||||
of typedFunction._typedFunctionData.signatureMap) {
|
||||
@ -1411,7 +1535,33 @@ export default class PocomathInstance {
|
||||
}
|
||||
if (allMatched) return details
|
||||
}
|
||||
}
|
||||
if (!(this._imps[name])) return undefined
|
||||
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
|
||||
if (foundsig) {
|
||||
if (haveTF) {
|
||||
try {
|
||||
return this._typed.findSignature(typedFunction, foundsig)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
try {
|
||||
return this._metaTyped.findSignature(this._meta[name], foundsig)
|
||||
} catch {
|
||||
}
|
||||
// We have an implementation but not a typed function. Do the best
|
||||
// we can:
|
||||
const foundImpl = this._imps[name][foundsig]
|
||||
const needs = {}
|
||||
for (const dep of foundImpl.uses) {
|
||||
const [base, sig] = dep.split('()')
|
||||
needs[dep] = this.resolve(base, sig)
|
||||
}
|
||||
const pseudoImpl = foundImpl.does(needs)
|
||||
return {fn: pseudoImpl, implementation: pseudoImpl}
|
||||
}
|
||||
// Hmm, no luck. Make sure bundle is up-to-date and retry:
|
||||
let result = undefined
|
||||
typedFunction = this[name]
|
||||
try {
|
||||
result = this._typed.findSignature(typedFunction, sig)
|
||||
|
@ -1,7 +1,9 @@
|
||||
import Returns from '../core/Returns.mjs'
|
||||
export const abs = {
|
||||
T: ({
|
||||
T,
|
||||
'smaller(T,T)': lt,
|
||||
'negate(T)': neg,
|
||||
'zero(T)': zr
|
||||
}) => t => (smaller(t, zr(t)) ? neg(t) : t)
|
||||
}) => Returns(T, t => (smaller(t, zr(t)) ? neg(t) : t))
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Returns from '../core/Returns.mjs'
|
||||
export * from './Types/number.mjs'
|
||||
|
||||
export const abs = {number: () => n => Math.abs(n)}
|
||||
export const abs = {'T:number': ({T}) => Returns(T, n => Math.abs(n))}
|
||||
|
@ -2,24 +2,22 @@
|
||||
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||
|
||||
const Tuple = new PocomathInstance('Tuple')
|
||||
// First a base type that will generally not be used directly
|
||||
Tuple.installType('Tuple', {
|
||||
test: t => t && typeof t === 'object' && 'elts' in t && Array.isArray(t.elts)
|
||||
})
|
||||
// Now the template type that is the primary use of this
|
||||
|
||||
Tuple.installType('Tuple<T>', {
|
||||
// We are assuming that any 'Type<T>' refines 'Type', so this is
|
||||
// not necessary:
|
||||
// refines: 'Tuple',
|
||||
// But we need there to be a way to determine the type of a tuple:
|
||||
infer: ({typeOf, joinTypes}) => t => joinTypes(t.elts.map(typeOf)),
|
||||
// For the test, we can assume that t is already a base tuple,
|
||||
// and we get the test for T as an input and we have to return
|
||||
// the test for Tuple<T>
|
||||
// A test that "defines" the "base type", which is not really a type
|
||||
// (only fully instantiated types are added to the universe)
|
||||
base: t => t && typeof t === 'object' && 'elts' in t && Array.isArray(t.elts),
|
||||
// The template portion of the test; it takes the test for T as
|
||||
// input and returns the test for an entity _that already passes
|
||||
// the base test_ to be a Tuple<T>:
|
||||
test: testT => t => t.elts.every(testT),
|
||||
// These are only invoked for types U such that there is already
|
||||
// a conversion from U to T, and that conversion is passed as an input
|
||||
// and we have to return the conversion to Tuple<T>:
|
||||
// And we need there to be a way to determine the (instantiation)
|
||||
// type of an tuple (that has already passed the base test):
|
||||
infer: ({typeOf, joinTypes}) => t => joinTypes(t.elts.map(typeOf)),
|
||||
// Conversions. Parametrized conversions are only invoked for types
|
||||
// U such that there is already a conversion from U to T, and that
|
||||
// conversion is passed as an input, and we have to return the conversion
|
||||
// function from the indicated template in terms of U to Tuple<T>:
|
||||
from: {
|
||||
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
|
||||
// Here since there is no U it's a straight conversion:
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Returns from '../core/Returns.mjs'
|
||||
export {Tuple} from './Types/Tuple.mjs'
|
||||
|
||||
export const length = {Tuple: () => t => t.elts.length}
|
||||
export const length = {'Tuple<T>': () => Returns('NumInt', t => t.elts.length)}
|
||||
|
@ -46,6 +46,9 @@ describe('The default full pocomath instance "math"', () => {
|
||||
assert.strictEqual(math.returnTypeOf('identity', 'Fraction'), 'Fraction')
|
||||
assert.strictEqual(
|
||||
math.returnTypeOf('quotient', 'bigint,bigint'), 'bigint')
|
||||
math.abs(math.complex(2,1)) //TODO: ditto
|
||||
assert.strictEqual(
|
||||
math.returnTypeOf('abs','Complex<NumInt>'), 'number')
|
||||
})
|
||||
|
||||
it('can subtract numbers', () => {
|
||||
|
@ -135,13 +135,8 @@ describe('A custom instance', () => {
|
||||
assert.strictEqual(
|
||||
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
||||
'Merge to Complex<number>')
|
||||
// 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
|
||||
assert.strictEqual(inst.typeMerge(3, inst.complex(3n)), 'Merge to Complex')
|
||||
// But types that truly cannot be merged should throw a TypeError
|
||||
// Should add a variation of this with a more usual type once there is
|
||||
// one not interconvertible with others...
|
||||
assert.throws(
|
||||
() => inst.typeMerge(3, inst.complex(3n)), TypeError)
|
||||
inst.install(genericSubtract)
|
||||
assert.throws(() => inst.typeMerge(3, undefined), TypeError)
|
||||
})
|
||||
|
@ -8,14 +8,12 @@ describe('tuple', () => {
|
||||
|
||||
it('does not allow unification by converting consecutive arguments', () => {
|
||||
assert.throws(() => math.tuple(3, 5.2, 2n), /TypeError.*unif/)
|
||||
// Hence, the order matters in a slightly unfortunate way,
|
||||
// but I think being a little ragged in these edge cases is OK:
|
||||
assert.throws(
|
||||
() => math.tuple(3, 2n, math.complex(5.2)),
|
||||
/TypeError.*unif/)
|
||||
assert.deepStrictEqual(
|
||||
math.tuple(3, math.complex(2n), 5.2),
|
||||
{elts: [math.complex(3), math.complex(2n), math.complex(5.2)]})
|
||||
assert.throws(
|
||||
() => math.tuple(3, math.complex(2n), 5.2),
|
||||
/TypeError.*unif/)
|
||||
})
|
||||
|
||||
it('can be tested for zero and equality', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user