feat: Return type annotations #53
@ -2,15 +2,13 @@ import {Returns, returnTypeOf} from '../../core/Returns.mjs'
|
|||||||
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
|
||||||
const Complex = new PocomathInstance('Complex')
|
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
|
// 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>', {
|
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),
|
test: testT => z => testT(z.re) && testT(z.im),
|
||||||
|
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
|
||||||
from: {
|
from: {
|
||||||
T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T)
|
T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T)
|
||||||
U: convert => u => {
|
U: convert => u => {
|
||||||
|
@ -3,6 +3,7 @@ export * from './Types/Complex.mjs'
|
|||||||
|
|
||||||
export const abs = {
|
export const abs = {
|
||||||
'Complex<T>': ({
|
'Complex<T>': ({
|
||||||
|
T,
|
||||||
sqrt, // Unfortunately no notation yet for the needed signature
|
sqrt, // Unfortunately no notation yet for the needed signature
|
||||||
'absquare(T)': baseabsq,
|
'absquare(T)': baseabsq,
|
||||||
'absquare(Complex<T>)': absq
|
'absquare(Complex<T>)': absq
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import Returns from '../core/Returns.mjs'
|
||||||
export * from './Types/Complex.mjs'
|
export * from './Types/Complex.mjs'
|
||||||
|
|
||||||
/* Returns true if w is z multiplied by a complex unit */
|
/* Returns true if w is z multiplied by a complex unit */
|
||||||
@ -9,9 +10,9 @@ export const associate = {
|
|||||||
'one(T)': uno,
|
'one(T)': uno,
|
||||||
'complex(T,T)': cplx,
|
'complex(T,T)': cplx,
|
||||||
'negate(Complex<T>)': neg
|
'negate(Complex<T>)': neg
|
||||||
}) => (w,z) => {
|
}) => Returns('boolean', (w,z) => {
|
||||||
if (eq(w,z) || eq(w,neg(z))) return true
|
if (eq(w,z) || eq(w,neg(z))) return true
|
||||||
const ti = times(z, cplx(zr(z.re), uno(z.im)))
|
const ti = times(z, cplx(zr(z.re), uno(z.im)))
|
||||||
return eq(w,ti) || eq(w,neg(ti))
|
return eq(w,ti) || eq(w,neg(ti))
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,15 @@ export const multiply = {
|
|||||||
T,
|
T,
|
||||||
'complex(T,T)': cplx,
|
'complex(T,T)': cplx,
|
||||||
'add(T,T)': plus,
|
'add(T,T)': plus,
|
||||||
'subtract(T,T)': sub,
|
'subtract(T,T)': subt,
|
||||||
'self(T,T)': me,
|
'self(T,T)': me,
|
||||||
'conjugate(T)': conj // makes quaternion multiplication work
|
'conjugate(T)': conj // makes quaternion multiplication work
|
||||||
}) => Returns(
|
}) => Returns(
|
||||||
`Complex<${T}>`,
|
`Complex<${T}>`,
|
||||||
(w,z) => cplx(
|
(w,z) => {
|
||||||
sub(me(w.re, z.re), me(conj(w.im), z.im)),
|
const realpart = subt(me( w.re, z.re), me(conj(w.im), z.im))
|
||||||
plus(me(conj(w.re), z.im), me(w.im, z.re)))
|
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 theTemplateParam = 'T' // First pass: only allow this one exact parameter
|
||||||
const restTemplateParam = `...${theTemplateParam}`
|
const restTemplateParam = `...${theTemplateParam}`
|
||||||
|
const templateCall = `<${theTemplateParam}>`
|
||||||
const templateFromParam = 'U' // For defining covariant conversions
|
const templateFromParam = 'U' // For defining covariant conversions
|
||||||
|
|
||||||
/* Returns a new signature just like sig but with the parameter replaced by
|
/* 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)
|
return sig.replaceAll(pattern, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lastWhatToDo = null // used in an infinite descent check
|
||||||
|
|
||||||
export default class PocomathInstance {
|
export default class PocomathInstance {
|
||||||
/* Disallowed names for ops; beware, this is slightly non-DRY
|
/* Disallowed names for ops; beware, this is slightly non-DRY
|
||||||
* in that if a new top-level PocomathInstance method is added, its name
|
* in that if a new top-level PocomathInstance method is added, its name
|
||||||
@ -55,24 +58,39 @@ export default class PocomathInstance {
|
|||||||
this._affects = {}
|
this._affects = {}
|
||||||
this._typed = typed.create()
|
this._typed = typed.create()
|
||||||
this._typed.clear()
|
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 me = this
|
||||||
const myTyped = this._typed
|
const myTyped = this._typed
|
||||||
this._typed.onMismatch = (name, args, sigs) => {
|
this._typed.onMismatch = (name, args, sigs) => {
|
||||||
if (me._invalid.has(name)) {
|
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)
|
||||||
|
}
|
||||||
|
me._typed.throwMismatchError(name, args, sigs)
|
||||||
}
|
}
|
||||||
/* List of types installed in the instance. We start with just dummies
|
// List of types installed in the instance: (We start with just dummies
|
||||||
* for the 'any' type and for type parameters:
|
// for the 'any' type and for type parameters.)
|
||||||
*/
|
|
||||||
this.Types = {any: anySpec}
|
this.Types = {any: anySpec}
|
||||||
this.Types[theTemplateParam] = anySpec
|
this.Types[theTemplateParam] = anySpec
|
||||||
this.Types.ground = anySpec
|
// Types that have been moved into the metaverse:
|
||||||
// All the template types that have been defined
|
this._metafiedTypes = new Set()
|
||||||
|
// All the template types that have been defined:
|
||||||
this.Templates = {}
|
this.Templates = {}
|
||||||
// The actual type testing functions
|
// And their instantiations:
|
||||||
|
this._instantiationsOf = {}
|
||||||
|
// The actual type testing functions:
|
||||||
this._typeTests = {}
|
this._typeTests = {}
|
||||||
// For each type, gives all of its (in)direct subtypes in topo order:
|
// For each type, gives all of its (in)direct subtypes in topo order:
|
||||||
this._subtypes = {}
|
this._subtypes = {}
|
||||||
@ -87,27 +105,19 @@ export default class PocomathInstance {
|
|||||||
this._maxDepthSeen = 1 // deepest template nesting we've actually encountered
|
this._maxDepthSeen = 1 // deepest template nesting we've actually encountered
|
||||||
this._invalid = new Set() // methods that are currently invalid
|
this._invalid = new Set() // methods that are currently invalid
|
||||||
this._config = {predictable: false, epsilon: 1e-12}
|
this._config = {predictable: false, epsilon: 1e-12}
|
||||||
const self = this
|
|
||||||
this.config = new Proxy(this._config, {
|
this.config = new Proxy(this._config, {
|
||||||
get: (target, property) => target[property],
|
get: (target, property) => target[property],
|
||||||
set: (target, property, value) => {
|
set: (target, property, value) => {
|
||||||
if (value !== target[property]) {
|
if (value !== target[property]) {
|
||||||
target[property] = value
|
target[property] = value
|
||||||
self._invalidateDependents('config')
|
me._invalidateDependents('config')
|
||||||
}
|
}
|
||||||
return true // successful
|
return true // successful
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this._plainFunctions = new Set() // the names of the plain functions
|
this._plainFunctions = new Set() // the names of the plain functions
|
||||||
this._chainRepository = {} // place to store chainified functions
|
this._chainRepository = {} // place to store chainified functions
|
||||||
|
this.joinTypes = this.joinTypes.bind(me)
|
||||||
this._installFunctions({
|
|
||||||
typeOf: {
|
|
||||||
ground: {uses: new Set(), does: () => Returns('string', () => 'any')}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.joinTypes = this.joinTypes.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -330,9 +340,10 @@ export default class PocomathInstance {
|
|||||||
`Type name '${type}' reserved for template parameter`)
|
`Type name '${type}' reserved for template parameter`)
|
||||||
}
|
}
|
||||||
if (parts.some(this._templateParam.bind(this))) {
|
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)
|
return this._installTemplateType(type, spec)
|
||||||
}
|
}
|
||||||
|
const base = parts[0]
|
||||||
if (type in this.Types) {
|
if (type in this.Types) {
|
||||||
if (spec !== this.Types[type]) {
|
if (spec !== this.Types[type]) {
|
||||||
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
||||||
@ -345,7 +356,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
let beforeType = spec.refines
|
let beforeType = spec.refines
|
||||||
if (!beforeType) {
|
if (!beforeType) {
|
||||||
beforeType = 'ground'
|
beforeType = 'any'
|
||||||
for (const other of spec.before || []) {
|
for (const other of spec.before || []) {
|
||||||
if (other in this.Types) {
|
if (other in this.Types) {
|
||||||
beforeType = other
|
beforeType = other
|
||||||
@ -387,6 +398,21 @@ export default class PocomathInstance {
|
|||||||
for (const subtype of this._subtypes[from]) {
|
for (const subtype of this._subtypes[from]) {
|
||||||
this._priorTypes[nextSuper].add(subtype)
|
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
|
nextSuper = this.Types[nextSuper].refines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,6 +437,16 @@ export default class PocomathInstance {
|
|||||||
convert: this.Types[to].from[fromtype]
|
convert: this.Types[to].from[fromtype]
|
||||||
})
|
})
|
||||||
this._invalidateDependents(':' + nextSuper)
|
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 {
|
} catch {
|
||||||
}
|
}
|
||||||
this._priorTypes[nextSuper].add(type)
|
this._priorTypes[nextSuper].add(type)
|
||||||
@ -425,6 +461,12 @@ export default class PocomathInstance {
|
|||||||
this._installFunctions({typeOf: imp})
|
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) {
|
_addSubtypeTo(sup, sub) {
|
||||||
if (this.isSubtypeOf(sub, sup)) return
|
if (this.isSubtypeOf(sub, sup)) return
|
||||||
const supSubs = this._subtypes[sup]
|
const supSubs = this._subtypes[sup]
|
||||||
@ -435,8 +477,10 @@ export default class PocomathInstance {
|
|||||||
supSubs.splice(i, 0, sub)
|
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) {
|
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)
|
return this._subtypes[typeB].includes(typeA)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -483,7 +527,6 @@ export default class PocomathInstance {
|
|||||||
if (!typeA) return typeB
|
if (!typeA) return typeB
|
||||||
if (!typeB) return typeA
|
if (!typeB) return typeA
|
||||||
if (typeA === 'any' || typeB === 'any') return 'any'
|
if (typeA === 'any' || typeB === 'any') return 'any'
|
||||||
if (typeA === 'ground' || typeB === 'ground') return 'ground'
|
|
||||||
if (typeA === typeB) return typeA
|
if (typeA === typeB) return typeA
|
||||||
const subber = convert ? this._priorTypes : this._subtypes
|
const subber = convert ? this._priorTypes : this._subtypes
|
||||||
const pick = convert ? 'has' : 'includes'
|
const pick = convert ? 'has' : 'includes'
|
||||||
@ -510,7 +553,8 @@ export default class PocomathInstance {
|
|||||||
* signatures of operations, but which have not actually been installed:
|
* signatures of operations, but which have not actually been installed:
|
||||||
*/
|
*/
|
||||||
undefinedTypes = Returns('Array<string>', function() {
|
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 */
|
/* Used internally to install a template type */
|
||||||
@ -526,6 +570,18 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
return
|
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
|
// update the typeOf function
|
||||||
const imp = {}
|
const imp = {}
|
||||||
imp[type] = {
|
imp[type] = {
|
||||||
@ -534,6 +590,9 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
this._installFunctions({typeOf: imp})
|
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
|
// Nothing else actually happens until we match a template parameter
|
||||||
this.Templates[base] = {type, spec}
|
this.Templates[base] = {type, spec}
|
||||||
}
|
}
|
||||||
@ -578,12 +637,11 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
opImps[signature] = {
|
opImps[signature] = {
|
||||||
explicit,
|
explicit,
|
||||||
|
resolved: false,
|
||||||
uses: behavior.uses,
|
uses: behavior.uses,
|
||||||
does: behavior.does
|
does: behavior.does
|
||||||
}
|
}
|
||||||
if (explicit) {
|
if (!explicit) {
|
||||||
opImps[signature].resolved = false
|
|
||||||
} else {
|
|
||||||
opImps[signature].hasInstantiations = {}
|
opImps[signature].hasInstantiations = {}
|
||||||
opImps[signature].needsInstantiations = new Set()
|
opImps[signature].needsInstantiations = new Set()
|
||||||
}
|
}
|
||||||
@ -628,6 +686,8 @@ export default class PocomathInstance {
|
|||||||
_invalidate(name, reason) {
|
_invalidate(name, reason) {
|
||||||
if (!(name in this._imps)) {
|
if (!(name in this._imps)) {
|
||||||
this._imps[name] = {}
|
this._imps[name] = {}
|
||||||
|
this._TFimps[name] = {}
|
||||||
|
this._metaTFimps[name] = {}
|
||||||
}
|
}
|
||||||
if (reason) {
|
if (reason) {
|
||||||
// Make sure no TF imps that depend on reason remain:
|
// Make sure no TF imps that depend on reason remain:
|
||||||
@ -692,10 +752,8 @@ export default class PocomathInstance {
|
|||||||
if (!imps) {
|
if (!imps) {
|
||||||
throw new SyntaxError(`No implementations for ${name}`)
|
throw new SyntaxError(`No implementations for ${name}`)
|
||||||
}
|
}
|
||||||
if (!(this._TFimps[name])) {
|
|
||||||
this._TFimps[name] = {}
|
|
||||||
}
|
|
||||||
const tf_imps = this._TFimps[name]
|
const tf_imps = this._TFimps[name]
|
||||||
|
const meta_imps = this._metaTFimps[name]
|
||||||
/* Collect the entries we know the types for */
|
/* Collect the entries we know the types for */
|
||||||
const usableEntries = []
|
const usableEntries = []
|
||||||
for (const entry of Object.entries(imps)) {
|
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) {
|
for (const instType of instantiationSet) {
|
||||||
if (!(instType in this.Types)) continue
|
if (!(instType in this.Types)) continue
|
||||||
if (this.Types[instType] === anySpec) continue
|
if (this.Types[instType] === anySpec) continue
|
||||||
@ -785,8 +916,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
return behavior.does(innerRefs)
|
return behavior.does(innerRefs)
|
||||||
}
|
}
|
||||||
this._addTFimplementation(
|
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
||||||
tf_imps, signature, {uses, does: patch})
|
|
||||||
tf_imps[signature]._pocoSignature = rawSignature
|
tf_imps[signature]._pocoSignature = rawSignature
|
||||||
tf_imps[signature]._pocoInstance = instType
|
tf_imps[signature]._pocoInstance = instType
|
||||||
behavior.hasInstantiations[instType] = signature
|
behavior.hasInstantiations[instType] = signature
|
||||||
@ -794,13 +924,7 @@ export default class PocomathInstance {
|
|||||||
/* Now add the catchall signature */
|
/* Now add the catchall signature */
|
||||||
/* (Not needed if if it's a bounded template) */
|
/* (Not needed if if it's a bounded template) */
|
||||||
if (ubType) continue
|
if (ubType) continue
|
||||||
if ('_catchall_' in behavior.hasInstantiations) continue
|
if (behavior.resolved) 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')
|
|
||||||
/* The catchall signature has to detect the actual type of the call
|
/* The catchall signature has to detect the actual type of the call
|
||||||
* and add the new instantiations.
|
* and add the new instantiations.
|
||||||
* First, prepare the type inference data:
|
* First, prepare the type inference data:
|
||||||
@ -840,150 +964,124 @@ export default class PocomathInstance {
|
|||||||
/* For return type annotation, we may have to fix this to
|
/* For return type annotation, we may have to fix this to
|
||||||
propagate the return type. At the moment we are just bagging
|
propagate the return type. At the moment we are just bagging
|
||||||
*/
|
*/
|
||||||
const patch = (refs) => (...args) => {
|
const patch = () => {
|
||||||
/* We unbundle the rest arg if there is one */
|
const patchFunc = (...tfBundledArgs) => {
|
||||||
const regLength = args.length - 1
|
/* We unbundle the rest arg if there is one */
|
||||||
if (restParam) {
|
let args = Array.from(tfBundledArgs)
|
||||||
const restArgs = args.pop()
|
const regLength = args.length - 1
|
||||||
args = args.concat(restArgs)
|
if (restParam) {
|
||||||
}
|
const restArgs = args.pop()
|
||||||
/* Now infer the type we actually should have been called for */
|
args = args.concat(restArgs)
|
||||||
let i = -1
|
|
||||||
let j = -1
|
|
||||||
/* collect the arg types */
|
|
||||||
const argTypes = []
|
|
||||||
for (const arg of args) {
|
|
||||||
++j
|
|
||||||
// in case of rest parameter, reuse last parameter type:
|
|
||||||
if (i < inferences.length - 1) ++i
|
|
||||||
if (inferences[i]) {
|
|
||||||
const argType = inferences[i](arg)
|
|
||||||
if (!argType) {
|
|
||||||
throw TypeError(
|
|
||||||
`Type inference failed for argument ${j} of ${name}`)
|
|
||||||
}
|
|
||||||
if (argType === 'any') {
|
|
||||||
throw TypeError(
|
|
||||||
`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?
|
|
||||||
+ args.map(a => a.toString()).join(', ')
|
|
||||||
+ ' of types ' + argTypes.join(', ') + argType)
|
|
||||||
}
|
|
||||||
argTypes.push(argType)
|
|
||||||
}
|
}
|
||||||
}
|
/* Now infer the type we actually should have been called for */
|
||||||
if (argTypes.length === 0) {
|
let i = -1
|
||||||
throw TypeError('Type inference failed for' + name)
|
let j = -1
|
||||||
}
|
/* collect the arg types */
|
||||||
let usedConversions = false
|
const argTypes = []
|
||||||
let instantiateFor = self.joinTypes(argTypes)
|
for (const arg of args) {
|
||||||
if (instantiateFor === 'any') {
|
++j
|
||||||
usedConversions = true
|
// in case of rest parameter, reuse last parameter type:
|
||||||
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
if (i < inferences.length - 1) ++i
|
||||||
|
if (inferences[i]) {
|
||||||
|
const argType = inferences[i](arg)
|
||||||
|
if (!argType) {
|
||||||
|
throw TypeError(
|
||||||
|
`Type inference failed for argument ${j} of ${name}`)
|
||||||
|
}
|
||||||
|
if (argType === 'any') {
|
||||||
|
throw TypeError(
|
||||||
|
`In call to ${name}, `
|
||||||
|
+ 'incompatible template arguments:'
|
||||||
|
// + args.map(a => JSON.stringify(a)).join(', ')
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
argTypes.push(argType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (argTypes.length === 0) {
|
||||||
|
throw TypeError('Type inference failed for' + name)
|
||||||
|
}
|
||||||
|
let usedConversions = false
|
||||||
|
let instantiateFor = self.joinTypes(argTypes)
|
||||||
if (instantiateFor === 'any') {
|
if (instantiateFor === 'any') {
|
||||||
throw TypeError(
|
usedConversions = true
|
||||||
`In call to ${name}, no type unifies arguments `
|
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
||||||
+ args.toString() + '; of types ' + argTypes.toString()
|
if (instantiateFor === 'any') {
|
||||||
+ '; note each consecutive pair must unify to a '
|
throw TypeError(
|
||||||
+ 'supertype of at least one of them')
|
`In call to ${name}, no type unifies arguments `
|
||||||
}
|
+ args.toString() + '; of types ' + argTypes.toString()
|
||||||
}
|
+ '; note each consecutive pair must unify to a '
|
||||||
const depth = instantiateFor.split('<').length
|
+ 'supertype of at least one of them')
|
||||||
if (depth > self._maxDepthSeen) {
|
|
||||||
self._maxDepthSeen = depth
|
|
||||||
}
|
|
||||||
/* Generate the list of actual wanted types */
|
|
||||||
const wantTypes = parTypes.map(type => substituteInSignature(
|
|
||||||
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"
|
|
||||||
*/
|
|
||||||
for (j = 0; j < parTypes.length; ++j) {
|
|
||||||
if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) {
|
|
||||||
// actually used the param and is a template
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
const depth = instantiateFor.split('<').length
|
||||||
/* Finally reassemble the rest args if there were any */
|
if (depth > self._maxDepthSeen) {
|
||||||
if (restParam) {
|
self._maxDepthSeen = depth
|
||||||
const restArgs = args.slice(regLength)
|
}
|
||||||
args = args.slice(0,regLength)
|
/* Generate the list of actual wanted types */
|
||||||
args.push(restArgs)
|
const wantTypes = parTypes.map(type => substituteInSignature(
|
||||||
}
|
type, theTemplateParam, instantiateFor))
|
||||||
/* Arrange that the desired instantiation will be there next
|
const wantSig = wantTypes.join(',')
|
||||||
* time so we don't have to go through that again for this type
|
/* Now we have to add any actual types that are relevant
|
||||||
*/
|
* to this invocation. Namely, that would be every formal
|
||||||
behavior.needsInstantiations.add(instantiateFor)
|
* parameter type in the invocation, with the parameter
|
||||||
self._invalidate(name)
|
* template instantiated by instantiateFor, and for all of
|
||||||
// And update refs because we now know the type we're instantiating
|
* instantiateFor's "prior types"
|
||||||
// for:
|
*/
|
||||||
refs[theTemplateParam] = instantiateFor
|
for (j = 0; j < parTypes.length; ++j) {
|
||||||
const innerRefs = {}
|
if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) {
|
||||||
for (const dep in simplifiedUses) {
|
// actually used the param and is a template
|
||||||
const simplifiedDep = simplifiedUses[dep]
|
self._ensureTemplateTypes(parTypes[j], instantiateFor)
|
||||||
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.
|
/* Request the desired instantiation: */
|
||||||
const implementation = behavior.does(innerRefs)
|
// But possibly since this resolution was grabbed, the proper
|
||||||
// We can access return type information here
|
// instantiation has been added (like if there are multiple
|
||||||
// And in particular, if it might be a template, we should try to
|
// uses in the implementation of another method.
|
||||||
// instantiate it:
|
if (!(behavior.needsInstantiations.has(instantiateFor))) {
|
||||||
const returnType = returnTypeOf(implementation, wantSig, self)
|
behavior.needsInstantiations.add(instantiateFor)
|
||||||
for (const possibility of returnType.split('|')) {
|
self._invalidate(name)
|
||||||
const instantiated = self._maybeInstantiate(possibility)
|
|
||||||
if (instantiated) {
|
|
||||||
const tempBase = instantiated.split('<',1)[0]
|
|
||||||
self._invalidateDependents(':' + tempBase)
|
|
||||||
}
|
}
|
||||||
|
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(whatToDo.fn, wantSig, self)
|
||||||
|
for (const possibility of returnType.split('|')) {
|
||||||
|
const instantiated = self._maybeInstantiate(possibility)
|
||||||
|
if (instantiated) {
|
||||||
|
const tempBase = instantiated.split('<',1)[0]
|
||||||
|
self._invalidateDependents(':' + tempBase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (whatToDo === lastWhatToDo) {
|
||||||
|
throw new Error(
|
||||||
|
`Infinite recursion in resolving $name called on`
|
||||||
|
+ args.map(x => x.toString()).join(','))
|
||||||
|
}
|
||||||
|
lastWhatToDo = whatToDo
|
||||||
|
const retval = whatToDo.implementation(...args)
|
||||||
|
lastWhatToDo = null
|
||||||
|
return retval
|
||||||
}
|
}
|
||||||
return implementation(...args)
|
Object.defineProperty(
|
||||||
|
patchFunc, 'name', {value: `${name}(${signature})`})
|
||||||
|
return patchFunc
|
||||||
}
|
}
|
||||||
Object.defineProperty(patch, 'name', {value: `${name}(${signature})`})
|
Object.defineProperty(
|
||||||
// TODO: Decorate patch with a function that calculates the
|
patch, 'name', {value: `generate[${name}(${signature})]`})
|
||||||
|
// TODO?: Decorate patch with a function that calculates the
|
||||||
// correct return type a priori. Deferring because unclear what
|
// correct return type a priori. Deferring because unclear what
|
||||||
// aspects will be merged into typed-function.
|
// 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(
|
this._addTFimplementation(
|
||||||
tf_imps, signature, {uses: outerUses, does: patch})
|
meta_imps, signature, {uses: new Set(), does: patch})
|
||||||
behavior.hasInstantiations._catchall_ = rawSignature
|
behavior.resolved = true
|
||||||
}
|
}
|
||||||
this._correctPartialSelfRefs(name, tf_imps)
|
this._correctPartialSelfRefs(name, tf_imps)
|
||||||
// Make sure we have all of the needed (template) types; and if they
|
// 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]
|
delete fromBehavior.hasInstantiations[imp._pocoInstance]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const tf = this._typed(name, tf_imps)
|
let tf
|
||||||
Object.defineProperty(tf, 'fromInstance', {value: this})
|
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})
|
Object.defineProperty(this, name, {configurable: true, value: tf})
|
||||||
return tf
|
return tf
|
||||||
}
|
}
|
||||||
@ -1077,7 +1186,10 @@ export default class PocomathInstance {
|
|||||||
'typed-function does not support mixed full and '
|
'typed-function does not support mixed full and '
|
||||||
+ 'partial self-reference')
|
+ '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)
|
part_self_references.push(needsig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1097,10 +1209,19 @@ export default class PocomathInstance {
|
|||||||
* accumulating:
|
* accumulating:
|
||||||
*/
|
*/
|
||||||
if (needsig) {
|
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
|
let result = undefined
|
||||||
try {
|
try {
|
||||||
result = this._typed.find(tempTF, needsig, {exact: true})
|
result = typedUniverse.find(tempTF, needsig, {exact: true})
|
||||||
} catch {}
|
} catch {}
|
||||||
if (result) {
|
if (result) {
|
||||||
refs[dep] = result
|
refs[dep] = result
|
||||||
@ -1158,7 +1279,7 @@ export default class PocomathInstance {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const implementation = does(refs)
|
const implementation = does(refs)
|
||||||
// could do something with return type information here
|
// could do something with return type information here?
|
||||||
imps[signature] = implementation
|
imps[signature] = implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1168,38 +1289,53 @@ export default class PocomathInstance {
|
|||||||
const deferral = imps[aSignature]
|
const deferral = imps[aSignature]
|
||||||
const part_self_references = deferral.psr
|
const part_self_references = deferral.psr
|
||||||
const corrected_self_references = []
|
const corrected_self_references = []
|
||||||
|
const remaining_self_references = []
|
||||||
|
const refs = deferral.builtRefs
|
||||||
for (const neededSig of part_self_references) {
|
for (const neededSig of part_self_references) {
|
||||||
// Have to find a match for neededSig among the other signatures
|
// Have to find a match for neededSig among the other signatures
|
||||||
// of this function. That's a job for typed-function, but we will
|
// of this function. That's a job for typed-function, but we will
|
||||||
// try here:
|
// try here:
|
||||||
if (neededSig in imps) { // the easy case
|
if (neededSig in imps) { // the easy case
|
||||||
corrected_self_references.push(neededSig)
|
corrected_self_references.push(neededSig)
|
||||||
|
remaining_self_references.push(neededSig)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// No exact match, try to get one that matches with
|
// No exact match, try to get one that matches with
|
||||||
// subtypes since the whole conversion thing in typed-function
|
// subtypes since the whole conversion thing in typed-function
|
||||||
// is too complicated to reproduce
|
// is too complicated to reproduce
|
||||||
const foundSig = this._findSubtypeImpl(name, imps, neededSig)
|
let foundSig = this._findSubtypeImpl(name, imps, neededSig)
|
||||||
if (foundSig) {
|
if (foundSig) {
|
||||||
corrected_self_references.push(foundSig)
|
corrected_self_references.push(foundSig)
|
||||||
|
remaining_self_references.push(neededSig)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
// Maybe it's a template instance we don't yet have
|
||||||
'Implement inexact self-reference in typed-function for '
|
foundSig = this._findSubtypeImpl(
|
||||||
+ `${name}(${neededSig})`)
|
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
|
const does = deferral.sigDoes
|
||||||
imps[aSignature] = this._typed.referTo(
|
if (remaining_self_references.length > 0) {
|
||||||
...corrected_self_references, (...impls) => {
|
imps[aSignature] = this._typed.referTo(
|
||||||
for (let i = 0; i < part_self_references.length; ++i) {
|
...corrected_self_references, (...impls) => {
|
||||||
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
|
||||||
}
|
}
|
||||||
const implementation = does(refs)
|
)
|
||||||
// What will we do with the return type info in here?
|
} else {
|
||||||
return implementation
|
imps[aSignature] = does(refs)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
imps[aSignature]._pocoSignature = deferral._pocoSignature
|
imps[aSignature]._pocoSignature = deferral._pocoSignature
|
||||||
imps[aSignature]._pocoInstance = deferral._pocoInstance
|
imps[aSignature]._pocoInstance = deferral._pocoInstance
|
||||||
}
|
}
|
||||||
@ -1247,7 +1383,7 @@ export default class PocomathInstance {
|
|||||||
if (wantsType in this.Types) return false
|
if (wantsType in this.Types) return false
|
||||||
// OK, need to generate the type from the template
|
// OK, need to generate the type from the template
|
||||||
// Set up refines, before, test, and from
|
// Set up refines, before, test, and from
|
||||||
const newTypeSpec = {refines: base}
|
const newTypeSpec = {}
|
||||||
const maybeFrom = {}
|
const maybeFrom = {}
|
||||||
const template = this.Templates[base].spec
|
const template = this.Templates[base].spec
|
||||||
if (!template) {
|
if (!template) {
|
||||||
@ -1255,6 +1391,11 @@ export default class PocomathInstance {
|
|||||||
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
|
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
|
||||||
}
|
}
|
||||||
const instantiatorSpec = this.Types[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 = []
|
let beforeTypes = []
|
||||||
if (instantiatorSpec.before) {
|
if (instantiatorSpec.before) {
|
||||||
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
||||||
@ -1268,18 +1409,15 @@ export default class PocomathInstance {
|
|||||||
if (beforeTypes.length > 0) {
|
if (beforeTypes.length > 0) {
|
||||||
newTypeSpec.before = beforeTypes
|
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) {
|
if (template.from) {
|
||||||
for (let source in template.from) {
|
for (let source in template.from) {
|
||||||
const instSource = substituteInSignature(
|
const instSource = substituteInSignature(
|
||||||
source, theTemplateParam, instantiator)
|
source, theTemplateParam, instantiator)
|
||||||
let usesFromParam = false
|
const testSource = substituteInSignature(
|
||||||
for (const word of instSource.split(/[<>]/)) {
|
instSource, templateFromParam, instantiator)
|
||||||
if (word === templateFromParam) {
|
const usesFromParam = (testSource !== instSource)
|
||||||
usesFromParam = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (usesFromParam) {
|
if (usesFromParam) {
|
||||||
for (const iFrom in instantiatorSpec.from) {
|
for (const iFrom in instantiatorSpec.from) {
|
||||||
const finalSource = substituteInSignature(
|
const finalSource = substituteInSignature(
|
||||||
@ -1287,11 +1425,13 @@ export default class PocomathInstance {
|
|||||||
maybeFrom[finalSource] = template.from[source](
|
maybeFrom[finalSource] = template.from[source](
|
||||||
instantiatorSpec.from[iFrom])
|
instantiatorSpec.from[iFrom])
|
||||||
}
|
}
|
||||||
// Assuming all templates are covariant here, I guess...
|
if (testSource !== wantsType) { // subtypes handled with refines
|
||||||
for (const subType of this._subtypes[instantiator]) {
|
// Assuming all templates are covariant here, I guess...
|
||||||
const finalSource = substituteInSignature(
|
for (const subType of this._subtypes[instantiator]) {
|
||||||
instSource, templateFromParam, subType)
|
const finalSource = substituteInSignature(
|
||||||
maybeFrom[finalSource] = template.from[source](x => x)
|
instSource, templateFromParam, subType)
|
||||||
|
maybeFrom[finalSource] = template.from[source](x => x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maybeFrom[instSource] = template.from[source]
|
maybeFrom[instSource] = template.from[source]
|
||||||
@ -1303,6 +1443,7 @@ export default class PocomathInstance {
|
|||||||
newTypeSpec.from = maybeFrom
|
newTypeSpec.from = maybeFrom
|
||||||
}
|
}
|
||||||
this.installType(wantsType, newTypeSpec)
|
this.installType(wantsType, newTypeSpec)
|
||||||
|
this._instantiationsOf[base].add(wantsType)
|
||||||
return wantsType
|
return wantsType
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1314,7 +1455,7 @@ export default class PocomathInstance {
|
|||||||
const otherTypeList = typeListOfSignature(otherSig)
|
const otherTypeList = typeListOfSignature(otherSig)
|
||||||
if (typeList.length !== otherTypeList.length) continue
|
if (typeList.length !== otherTypeList.length) continue
|
||||||
let allMatch = true
|
let allMatch = true
|
||||||
let paramBound = 'ground'
|
let paramBound = 'any'
|
||||||
for (let k = 0; k < typeList.length; ++k) {
|
for (let k = 0; k < typeList.length; ++k) {
|
||||||
let myType = typeList[k]
|
let myType = typeList[k]
|
||||||
let otherType = otherTypeList[k]
|
let otherType = otherTypeList[k]
|
||||||
@ -1326,8 +1467,7 @@ export default class PocomathInstance {
|
|||||||
otherTypeList[k] = `...${paramBound}`
|
otherTypeList[k] = `...${paramBound}`
|
||||||
otherType = paramBound
|
otherType = paramBound
|
||||||
}
|
}
|
||||||
const adjustedOtherType = otherType.replaceAll(
|
const adjustedOtherType = otherType.replaceAll(templateCall, '')
|
||||||
`<${theTemplateParam}>`, '')
|
|
||||||
if (adjustedOtherType !== otherType) {
|
if (adjustedOtherType !== otherType) {
|
||||||
otherTypeList[k] = adjustedOtherType
|
otherTypeList[k] = adjustedOtherType
|
||||||
otherType = adjustedOtherType
|
otherType = adjustedOtherType
|
||||||
@ -1342,22 +1482,21 @@ export default class PocomathInstance {
|
|||||||
theTemplateParam, paramBound)
|
theTemplateParam, paramBound)
|
||||||
}
|
}
|
||||||
if (otherType === 'any') continue
|
if (otherType === 'any') continue
|
||||||
if (otherType === 'ground') continue
|
if (myType === otherType) continue
|
||||||
if (!(otherType in this.Types)) {
|
|
||||||
allMatch = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (myType === otherType
|
|
||||||
|| this.isSubtypeOf(myType, otherType)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (otherType in this.Templates) {
|
if (otherType in this.Templates) {
|
||||||
|
const myBase = myType.split('<',1)[0]
|
||||||
|
if (myBase === otherType) continue
|
||||||
if (this.instantiateTemplate(otherType, myType)) {
|
if (this.instantiateTemplate(otherType, myType)) {
|
||||||
let dummy
|
let dummy
|
||||||
dummy = this[name] // for side effects
|
dummy = this[name] // for side effects
|
||||||
return this._findSubtypeImpl(name, this._imps[name], neededSig)
|
return this._findSubtypeImpl(name, this._imps[name], neededSig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!(otherType in this.Types)) {
|
||||||
|
allMatch = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (this.isSubtypeOf(myType, otherType)) continue
|
||||||
allMatch = false
|
allMatch = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1373,19 +1512,42 @@ export default class PocomathInstance {
|
|||||||
if (!this._typed.isTypedFunction(typedFunction)) {
|
if (!this._typed.isTypedFunction(typedFunction)) {
|
||||||
typedFunction = this[name]
|
typedFunction = this[name]
|
||||||
}
|
}
|
||||||
let result = undefined
|
|
||||||
const haveTF = this._typed.isTypedFunction(typedFunction)
|
const haveTF = this._typed.isTypedFunction(typedFunction)
|
||||||
if (haveTF) {
|
if (haveTF) {
|
||||||
|
// First try a direct match
|
||||||
|
let result
|
||||||
try {
|
try {
|
||||||
result = this._typed.findSignature(typedFunction, sig, {exact: true})
|
result = this._typed.findSignature(typedFunction, sig, {exact: true})
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
|
if (result) return result
|
||||||
|
// Next, look ourselves but with subtypes:
|
||||||
|
const wantTypes = typeListOfSignature(sig)
|
||||||
|
for (const [implSig, details]
|
||||||
|
of typedFunction._typedFunctionData.signatureMap) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if (allMatched) return details
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result || !(this._imps[name])) return result
|
if (!(this._imps[name])) return undefined
|
||||||
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
|
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
|
||||||
if (foundsig) {
|
if (foundsig) {
|
||||||
if (haveTF) {
|
if (haveTF) {
|
||||||
return this._typed.findSignature(typedFunction, foundsig)
|
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 have an implementation but not a typed function. Do the best
|
||||||
// we can:
|
// we can:
|
||||||
@ -1398,20 +1560,8 @@ export default class PocomathInstance {
|
|||||||
const pseudoImpl = foundImpl.does(needs)
|
const pseudoImpl = foundImpl.does(needs)
|
||||||
return {fn: pseudoImpl, implementation: pseudoImpl}
|
return {fn: pseudoImpl, implementation: pseudoImpl}
|
||||||
}
|
}
|
||||||
const wantTypes = typeListOfSignature(sig)
|
|
||||||
for (const [implSig, details]
|
|
||||||
of typedFunction._typedFunctionData.signatureMap) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if (allMatched) return details
|
|
||||||
}
|
|
||||||
// Hmm, no luck. Make sure bundle is up-to-date and retry:
|
// Hmm, no luck. Make sure bundle is up-to-date and retry:
|
||||||
|
let result = undefined
|
||||||
typedFunction = this[name]
|
typedFunction = this[name]
|
||||||
try {
|
try {
|
||||||
result = this._typed.findSignature(typedFunction, sig)
|
result = this._typed.findSignature(typedFunction, sig)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import Returns from '../core/Returns.mjs'
|
||||||
export const abs = {
|
export const abs = {
|
||||||
T: ({
|
T: ({
|
||||||
|
T,
|
||||||
'smaller(T,T)': lt,
|
'smaller(T,T)': lt,
|
||||||
'negate(T)': neg,
|
'negate(T)': neg,
|
||||||
'zero(T)': zr
|
'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 * 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'
|
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||||
|
|
||||||
const Tuple = new PocomathInstance('Tuple')
|
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>', {
|
Tuple.installType('Tuple<T>', {
|
||||||
// We are assuming that any 'Type<T>' refines 'Type', so this is
|
// A test that "defines" the "base type", which is not really a type
|
||||||
// not necessary:
|
// (only fully instantiated types are added to the universe)
|
||||||
// refines: 'Tuple',
|
base: t => t && typeof t === 'object' && 'elts' in t && Array.isArray(t.elts),
|
||||||
// But we need there to be a way to determine the type of a tuple:
|
// The template portion of the test; it takes the test for T as
|
||||||
infer: ({typeOf, joinTypes}) => t => joinTypes(t.elts.map(typeOf)),
|
// input and returns the test for an entity _that already passes
|
||||||
// For the test, we can assume that t is already a base tuple,
|
// the base test_ to be a Tuple<T>:
|
||||||
// and we get the test for T as an input and we have to return
|
|
||||||
// the test for Tuple<T>
|
|
||||||
test: testT => t => t.elts.every(testT),
|
test: testT => t => t.elts.every(testT),
|
||||||
// These are only invoked for types U such that there is already
|
// And we need there to be a way to determine the (instantiation)
|
||||||
// a conversion from U to T, and that conversion is passed as an input
|
// type of an tuple (that has already passed the base test):
|
||||||
// and we have to return the conversion to Tuple<T>:
|
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: {
|
from: {
|
||||||
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
|
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
|
||||||
// Here since there is no U it's a straight conversion:
|
// 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 {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('identity', 'Fraction'), 'Fraction')
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
math.returnTypeOf('quotient', 'bigint,bigint'), 'bigint')
|
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', () => {
|
it('can subtract numbers', () => {
|
||||||
|
@ -39,8 +39,8 @@ describe('complex', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('checks for equality', () => {
|
it('checks for equality', () => {
|
||||||
assert.ok(math.equal(math.complex(3,0), 3))
|
assert.ok(math.equal(math.complex(3, 0), 3))
|
||||||
assert.ok(math.equal(math.complex(3,2), math.complex(3, 2)))
|
assert.ok(math.equal(math.complex(3, 2), math.complex(3, 2)))
|
||||||
assert.ok(!(math.equal(math.complex(45n, 3n), math.complex(45n, -3n))))
|
assert.ok(!(math.equal(math.complex(45n, 3n), math.complex(45n, -3n))))
|
||||||
assert.ok(!(math.equal(math.complex(45n, 3n), 45n)))
|
assert.ok(!(math.equal(math.complex(45n, 3n), 45n)))
|
||||||
})
|
})
|
||||||
|
@ -135,13 +135,8 @@ describe('A custom instance', () => {
|
|||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
||||||
'Merge to Complex<number>')
|
'Merge to Complex<number>')
|
||||||
// The following is the current behavior, since 3 converts to 3+0i
|
assert.throws(
|
||||||
// which is technically the same Complex type as 3n+0ni.
|
() => inst.typeMerge(3, inst.complex(3n)), TypeError)
|
||||||
// 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...
|
|
||||||
inst.install(genericSubtract)
|
inst.install(genericSubtract)
|
||||||
assert.throws(() => inst.typeMerge(3, undefined), TypeError)
|
assert.throws(() => inst.typeMerge(3, undefined), TypeError)
|
||||||
})
|
})
|
||||||
|
@ -8,14 +8,12 @@ describe('tuple', () => {
|
|||||||
|
|
||||||
it('does not allow unification by converting consecutive arguments', () => {
|
it('does not allow unification by converting consecutive arguments', () => {
|
||||||
assert.throws(() => math.tuple(3, 5.2, 2n), /TypeError.*unif/)
|
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(
|
assert.throws(
|
||||||
() => math.tuple(3, 2n, math.complex(5.2)),
|
() => math.tuple(3, 2n, math.complex(5.2)),
|
||||||
/TypeError.*unif/)
|
/TypeError.*unif/)
|
||||||
assert.deepStrictEqual(
|
assert.throws(
|
||||||
math.tuple(3, math.complex(2n), 5.2),
|
() => math.tuple(3, math.complex(2n), 5.2),
|
||||||
{elts: [math.complex(3), math.complex(2n), math.complex(5.2)]})
|
/TypeError.*unif/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can be tested for zero and equality', () => {
|
it('can be tested for zero and equality', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user