feat: Template types #45
@ -1,9 +1,11 @@
|
|||||||
import gcdType from '../generic/gcdType.mjs'
|
import gcdType from '../generic/gcdType.mjs'
|
||||||
|
import {identity} from '../generic/identity.mjs'
|
||||||
|
|
||||||
export * from './Types/bigint.mjs'
|
export * from './Types/bigint.mjs'
|
||||||
|
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {compare} from './compare.mjs'
|
export {compare} from './compare.mjs'
|
||||||
|
export const conjugate = {bigint: () => identity}
|
||||||
export {divide} from './divide.mjs'
|
export {divide} from './divide.mjs'
|
||||||
export const gcd = gcdType('bigint')
|
export const gcd = gcdType('bigint')
|
||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
|
@ -7,16 +7,16 @@ export const add = {
|
|||||||
*/
|
*/
|
||||||
'Complex,number': ({
|
'Complex,number': ({
|
||||||
'self(number,number)': addNum,
|
'self(number,number)': addNum,
|
||||||
'complex(any,any)': cplx
|
'complex(number,number)': cplx
|
||||||
}) => (z,x) => cplx(addNum(z.re, x), z.im),
|
}) => (z,x) => cplx(addNum(z.re, x), z.im),
|
||||||
|
|
||||||
'Complex,bigint': ({
|
'Complex,bigint': ({
|
||||||
'self(bigint,bigint)': addBigInt,
|
'self(bigint,bigint)': addBigInt,
|
||||||
'complex(any,any)': cplx
|
'complex(bigint,bigint)': cplx
|
||||||
}) => (z,x) => cplx(addBigInt(z.re, x), z.im),
|
}) => (z,x) => cplx(addBigInt(z.re, x), z.im),
|
||||||
|
|
||||||
'Complex,Complex': ({
|
'Complex,Complex': ({
|
||||||
self,
|
self,
|
||||||
'complex(any,any)': cplx
|
complex
|
||||||
}) => (w,z) => cplx(self(w.re, z.re), self(w.im, z.im))
|
}) => (w,z) => complex(self(w.re, z.re), self(w.im, z.im))
|
||||||
}
|
}
|
||||||
|
17
src/complex/associate.mjs
Normal file
17
src/complex/associate.mjs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export * from './Types/Complex.mjs'
|
||||||
|
|
||||||
|
/* Returns true if w is z multiplied by a complex unit */
|
||||||
|
export const associate = {
|
||||||
|
'Complex,Complex': ({
|
||||||
|
'multiply(Complex,Complex)': times,
|
||||||
|
'equalTT(Complex,Complex)': eq,
|
||||||
|
zero,
|
||||||
|
one,
|
||||||
|
complex,
|
||||||
|
'negate(Complex)': neg
|
||||||
|
}) => (w,z) => {
|
||||||
|
if (eq(w,z) || eq(w,neg(z))) return true
|
||||||
|
const ti = times(z, complex(zero(z.re), one(z.im)))
|
||||||
|
return eq(w,ti) || eq(w,neg(ti))
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,8 @@ export const complex = {
|
|||||||
'undefined': () => u => u,
|
'undefined': () => u => u,
|
||||||
'undefined,any': () => (u, y) => u,
|
'undefined,any': () => (u, y) => u,
|
||||||
'any,undefined': () => (x, u) => u,
|
'any,undefined': () => (x, u) => u,
|
||||||
'any,any': () => (x, y) => ({re: x, im: y}),
|
'undefined,undefined': () => (u, v) => u,
|
||||||
|
'T,T': () => (x, y) => ({re: x, im: y}),
|
||||||
/* Take advantage of conversions in typed-function */
|
/* Take advantage of conversions in typed-function */
|
||||||
Complex: () => z => z
|
Complex: () => z => z
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export * from './Types/Complex.mjs'
|
export * from './Types/Complex.mjs'
|
||||||
|
|
||||||
export const equal = {
|
export const equalTT = {
|
||||||
'Complex,number': ({
|
'Complex,number': ({
|
||||||
'isZero(number)': isZ,
|
'isZero(number)': isZ,
|
||||||
'self(number,number)': eqNum
|
'self(number,number)': eqNum
|
@ -3,15 +3,18 @@ import * as Complex from './Types/Complex.mjs'
|
|||||||
import gcdType from '../generic/gcdType.mjs'
|
import gcdType from '../generic/gcdType.mjs'
|
||||||
|
|
||||||
const imps = {
|
const imps = {
|
||||||
gcdComplexRaw: gcdType('Complex'),
|
gcdGIRaw: gcdType('GaussianInteger'),
|
||||||
gcd: { // Only return gcds with positive real part
|
gcd: { // Only return gcds with positive real part
|
||||||
'Complex, Complex': ({gcdComplexRaw, sign, one, negate}) => (z,m) => {
|
'GaussianInteger,GaussianInteger': ({
|
||||||
const raw = gcdComplexRaw(z, m)
|
'gcdGIRaw(GaussianInteger,GaussianInteger)': gcdRaw,
|
||||||
if (sign(raw.re) === one(raw.re)) return raw
|
'sign(bigint)': sgn,
|
||||||
return negate(raw)
|
'negate(GaussianInteger)': neg
|
||||||
|
}) => (z,m) => {
|
||||||
|
const raw = gcdRaw(z, m)
|
||||||
|
if (sgn(raw.re) === 1n) return raw
|
||||||
|
return neg(raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gcd = PocomathInstance.merge(Complex, imps)
|
export const gcd = PocomathInstance.merge(Complex, imps)
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import gcdType from '../generic/gcdType.mjs'
|
|
||||||
|
|
||||||
export * from './Types/Complex.mjs'
|
export * from './Types/Complex.mjs'
|
||||||
|
|
||||||
export {abs} from './abs.mjs'
|
export {abs} from './abs.mjs'
|
||||||
export {absquare} from './absquare.mjs'
|
export {absquare} from './absquare.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {conjugate} from './conjugate.mjs'
|
export {associate} from './associate.mjs'
|
||||||
export {complex} from './complex.mjs'
|
export {complex} from './complex.mjs'
|
||||||
export {equal} from './equal.mjs'
|
export {conjugate} from './conjugate.mjs'
|
||||||
|
export {equalTT} from './equalTT.mjs'
|
||||||
export {gcd} from './gcd.mjs'
|
export {gcd} from './gcd.mjs'
|
||||||
export {invert} from './invert.mjs'
|
export {invert} from './invert.mjs'
|
||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/* Core of pocomath: create an instance */
|
/* Core of pocomath: create an instance */
|
||||||
import typed from 'typed-function'
|
import typed from 'typed-function'
|
||||||
import dependencyExtractor from './dependencyExtractor.mjs'
|
import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
|
||||||
import {makeChain} from './Chain.mjs'
|
import {makeChain} from './Chain.mjs'
|
||||||
import {subsetOfKeys, typesOfSignature} from './utils.mjs'
|
import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs'
|
||||||
|
|
||||||
const anySpec = {} // fixed dummy specification of 'any' type
|
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 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
|
||||||
* the type
|
* the type
|
||||||
@ -27,7 +28,10 @@ export default class PocomathInstance {
|
|||||||
'importDependencies',
|
'importDependencies',
|
||||||
'install',
|
'install',
|
||||||
'installType',
|
'installType',
|
||||||
|
'joinTypes',
|
||||||
'name',
|
'name',
|
||||||
|
'self',
|
||||||
|
'Templates',
|
||||||
'typeOf',
|
'typeOf',
|
||||||
'Types',
|
'Types',
|
||||||
'undefinedTypes'
|
'undefinedTypes'
|
||||||
@ -39,11 +43,17 @@ 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}])
|
||||||
/* 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
|
||||||
|
// All the template types that have been defined
|
||||||
|
this.Templates = {}
|
||||||
|
// The actual type testing functions
|
||||||
|
this._typeTests = {}
|
||||||
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
||||||
/* The following gives for each type, a set of all types that could
|
/* The following gives for each type, a set of all types that could
|
||||||
* match in typed-function's dispatch algorithm before the given type.
|
* match in typed-function's dispatch algorithm before the given type.
|
||||||
@ -52,8 +62,8 @@ export default class PocomathInstance {
|
|||||||
* might match.
|
* might match.
|
||||||
*/
|
*/
|
||||||
this._priorTypes = {}
|
this._priorTypes = {}
|
||||||
this._usedTypes = new Set() // all types that have occurred in a signature
|
this._seenTypes = new Set() // all types that have occurred in a signature
|
||||||
this._doomed = new Set() // for detecting circular reference
|
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
|
const self = this
|
||||||
this.config = new Proxy(this._config, {
|
this.config = new Proxy(this._config, {
|
||||||
@ -68,6 +78,12 @@ export default class PocomathInstance {
|
|||||||
})
|
})
|
||||||
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._installFunctions({
|
||||||
|
typeOf: {ground: {uses: new Set(), does: () => () => 'any'}}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.joinTypes = this.joinTypes.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -177,9 +193,12 @@ export default class PocomathInstance {
|
|||||||
|
|
||||||
_installInstance(other) {
|
_installInstance(other) {
|
||||||
for (const [type, spec] of Object.entries(other.Types)) {
|
for (const [type, spec] of Object.entries(other.Types)) {
|
||||||
if (type === 'any' || this._templateParam(type)) continue
|
if (spec === anySpec) continue
|
||||||
this.installType(type, spec)
|
this.installType(type, spec)
|
||||||
}
|
}
|
||||||
|
for (const [base, info] of Object.entries(other.Templates)) {
|
||||||
|
this._installTemplateType(info.type, info.spec)
|
||||||
|
}
|
||||||
const migrateImps = {}
|
const migrateImps = {}
|
||||||
for (const operator in other._imps) {
|
for (const operator in other._imps) {
|
||||||
if (operator != 'typeOf') { // skip the builtin, we already have it
|
if (operator != 'typeOf') { // skip the builtin, we already have it
|
||||||
@ -262,10 +281,15 @@ export default class PocomathInstance {
|
|||||||
* the corresponding changes to the _typed object immediately
|
* the corresponding changes to the _typed object immediately
|
||||||
*/
|
*/
|
||||||
installType(type, spec) {
|
installType(type, spec) {
|
||||||
if (this._templateParam(type)) {
|
const parts = type.split(/[<,>]/)
|
||||||
|
if (this._templateParam(parts[0])) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Type name '${type}' reserved for template parameter`)
|
`Type name '${type}' reserved for template parameter`)
|
||||||
}
|
}
|
||||||
|
if (parts.some(this._templateParam.bind(this))) {
|
||||||
|
// It's a template, deal with it separately
|
||||||
|
return this._installTemplateType(type, spec)
|
||||||
|
}
|
||||||
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}`)
|
||||||
@ -278,7 +302,7 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
let beforeType = spec.refines
|
let beforeType = spec.refines
|
||||||
if (!beforeType) {
|
if (!beforeType) {
|
||||||
beforeType = 'any'
|
beforeType = 'ground'
|
||||||
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
|
||||||
@ -291,26 +315,44 @@ export default class PocomathInstance {
|
|||||||
const supertypeTest = this.Types[spec.refines].test
|
const supertypeTest = this.Types[spec.refines].test
|
||||||
testFn = entity => supertypeTest(entity) && spec.test(entity)
|
testFn = entity => supertypeTest(entity) && spec.test(entity)
|
||||||
}
|
}
|
||||||
|
this._typeTests[type] = testFn
|
||||||
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
||||||
this.Types[type] = spec
|
this.Types[type] = spec
|
||||||
|
this._subtypes[type] = new Set()
|
||||||
this._priorTypes[type] = new Set()
|
this._priorTypes[type] = new Set()
|
||||||
|
// Update all the subtype sets of supertypes up the chain
|
||||||
|
let nextSuper = spec.refines
|
||||||
|
while (nextSuper) {
|
||||||
|
this._invalidateDependents(':' + nextSuper)
|
||||||
|
this._priorTypes[nextSuper].add(type)
|
||||||
|
this._subtypes[nextSuper].add(type)
|
||||||
|
nextSuper = this.Types[nextSuper].refines
|
||||||
|
}
|
||||||
/* Now add conversions to this type */
|
/* Now add conversions to this type */
|
||||||
for (const from in (spec.from || {})) {
|
for (const from in (spec.from || {})) {
|
||||||
if (from in this.Types) {
|
if (from in this.Types) {
|
||||||
// add conversions from "from" to this one and all its supertypes:
|
// add conversions from "from" to this one and all its supertypes:
|
||||||
let nextSuper = type
|
let nextSuper = type
|
||||||
while (nextSuper) {
|
while (nextSuper) {
|
||||||
|
if (this._priorTypes[nextSuper].has(from)) break
|
||||||
this._typed.addConversion(
|
this._typed.addConversion(
|
||||||
{from, to: nextSuper, convert: spec.from[from]})
|
{from, to: nextSuper, convert: spec.from[from]})
|
||||||
this._invalidateDependents(':' + nextSuper)
|
this._invalidateDependents(':' + nextSuper)
|
||||||
this._priorTypes[nextSuper].add(from)
|
this._priorTypes[nextSuper].add(from)
|
||||||
|
/* And all of the subtypes of from are now prior as well: */
|
||||||
|
for (const subtype of this._subtypes[from]) {
|
||||||
|
this._priorTypes[nextSuper].add(subtype)
|
||||||
|
}
|
||||||
nextSuper = this.Types[nextSuper].refines
|
nextSuper = this.Types[nextSuper].refines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* And add conversions from this type */
|
/* And add conversions from this type */
|
||||||
for (const to in this.Types) {
|
for (const to in this.Types) {
|
||||||
if (type in (this.Types[to].from || {})) {
|
for (const fromtype in this.Types[to].from) {
|
||||||
|
if (type == fromtype
|
||||||
|
|| (fromtype in this._subtypes
|
||||||
|
&& this._subtypes[fromtype].has(type))) {
|
||||||
if (spec.refines == to || spec.refines in this._subtypes[to]) {
|
if (spec.refines == to || spec.refines in this._subtypes[to]) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Conversion of ${type} to its supertype ${to} disallowed.`)
|
`Conversion of ${type} to its supertype ${to} disallowed.`)
|
||||||
@ -320,7 +362,7 @@ export default class PocomathInstance {
|
|||||||
this._typed.addConversion({
|
this._typed.addConversion({
|
||||||
from: type,
|
from: type,
|
||||||
to: nextSuper,
|
to: nextSuper,
|
||||||
convert: this.Types[to].from[type]
|
convert: this.Types[to].from[fromtype]
|
||||||
})
|
})
|
||||||
this._invalidateDependents(':' + nextSuper)
|
this._invalidateDependents(':' + nextSuper)
|
||||||
this._priorTypes[nextSuper].add(type)
|
this._priorTypes[nextSuper].add(type)
|
||||||
@ -328,31 +370,74 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update all the subtype sets of supertypes up the chain, and
|
|
||||||
// while we are at it add trivial conversions from subtypes to supertypes
|
|
||||||
// to help typed-function match signatures properly:
|
|
||||||
this._subtypes[type] = new Set()
|
|
||||||
let nextSuper = spec.refines
|
|
||||||
while (nextSuper) {
|
|
||||||
this._typed.addConversion(
|
|
||||||
{from: type, to: nextSuper, convert: x => x})
|
|
||||||
this._invalidateDependents(':' + nextSuper)
|
|
||||||
this._priorTypes[nextSuper].add(type)
|
|
||||||
this._subtypes[nextSuper].add(type)
|
|
||||||
nextSuper = this.Types[nextSuper].refines
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the typeOf function
|
// update the typeOf function
|
||||||
const imp = {}
|
const imp = {}
|
||||||
imp[type] = {uses: new Set(), does: () => () => type}
|
imp[type] = {uses: new Set(), does: () => () => type}
|
||||||
this._installFunctions({typeOf: imp})
|
this._installFunctions({typeOf: imp})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns the most refined type of all the types in the array, with
|
||||||
|
* '' standing for the empty type for convenience. If the second
|
||||||
|
* argument `convert` is true, a convertible type is considered a
|
||||||
|
* a subtype (defaults to false).
|
||||||
|
*/
|
||||||
|
joinTypes(types, convert) {
|
||||||
|
let join = ''
|
||||||
|
for (const type of types) {
|
||||||
|
join = this._joinTypes(join, type, convert)
|
||||||
|
}
|
||||||
|
return join
|
||||||
|
}
|
||||||
|
/* helper for above */
|
||||||
|
_joinTypes(typeA, typeB, convert) {
|
||||||
|
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
|
||||||
|
if (subber[typeB].has(typeA)) return typeB
|
||||||
|
/* OK, so we need the most refined supertype of A that contains B:
|
||||||
|
*/
|
||||||
|
let nextSuper = typeA
|
||||||
|
while (nextSuper) {
|
||||||
|
if (subber[nextSuper].has(typeB)) return nextSuper
|
||||||
|
nextSuper = this.Types[nextSuper].refines
|
||||||
|
}
|
||||||
|
/* And if conversions are allowed, we have to search the other way too */
|
||||||
|
if (convert) {
|
||||||
|
nextSuper = typeB
|
||||||
|
while (nextSuper) {
|
||||||
|
if (subber[nextSuper].has(typeA)) return nextSuper
|
||||||
|
nextSuper = this.Types[nextSuper].refines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'any'
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns a list of all types that have been mentioned in the
|
/* Returns a list of all types that have been mentioned in the
|
||||||
* signatures of operations, but which have not actually been installed:
|
* signatures of operations, but which have not actually been installed:
|
||||||
*/
|
*/
|
||||||
undefinedTypes() {
|
undefinedTypes() {
|
||||||
return Array.from(this._usedTypes).filter(t => !(t in this.Types))
|
return Array.from(this._seenTypes).filter(t => !(t in this.Types))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used internally to install a template type */
|
||||||
|
_installTemplateType(type, spec) {
|
||||||
|
const base = type.split('<')[0]
|
||||||
|
/* For now, just allow a single template per base type; that
|
||||||
|
* might need to change later:
|
||||||
|
*/
|
||||||
|
if (base in this.Templates) {
|
||||||
|
if (spec !== this.Templates[base].spec) {
|
||||||
|
throw new SyntaxError(
|
||||||
|
`Conflicting definitions of template type ${type}`)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Nothing actually happens until we match a template parameter
|
||||||
|
this.Templates[base] = {type, spec}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Used internally by install, see the documentation there */
|
/* Used internally by install, see the documentation there */
|
||||||
@ -392,9 +477,12 @@ export default class PocomathInstance {
|
|||||||
this._addAffect(depname, name)
|
this._addAffect(depname, name)
|
||||||
}
|
}
|
||||||
for (const type of typesOfSignature(signature)) {
|
for (const type of typesOfSignature(signature)) {
|
||||||
if (this._templateParam(type)) continue
|
for (const word of type.split(/[<>]/)) {
|
||||||
this._usedTypes.add(type)
|
if (word.length == 0) continue
|
||||||
this._addAffect(':' + type, name)
|
if (this._templateParam(word)) continue
|
||||||
|
this._seenTypes.add(word)
|
||||||
|
this._addAffect(':' + word, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,20 +508,20 @@ export default class PocomathInstance {
|
|||||||
* and if it has no implementations so far, set them up.
|
* and if it has no implementations so far, set them up.
|
||||||
*/
|
*/
|
||||||
_invalidate(name) {
|
_invalidate(name) {
|
||||||
if (this._doomed.has(name)) {
|
if (this._invalid.has(name)) return
|
||||||
/* In the midst of a circular invalidation, so do nothing */
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!(name in this._imps)) {
|
if (!(name in this._imps)) {
|
||||||
this._imps[name] = {}
|
this._imps[name] = {}
|
||||||
}
|
}
|
||||||
this._doomed.add(name)
|
this._invalid.add(name)
|
||||||
this._invalidateDependents(name)
|
this._invalidateDependents(name)
|
||||||
this._doomed.delete(name)
|
|
||||||
const self = this
|
const self = this
|
||||||
Object.defineProperty(this, name, {
|
Object.defineProperty(this, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => self._bundle(name)
|
get: () => {
|
||||||
|
const result = self._bundle(name)
|
||||||
|
self._invalid.delete(name)
|
||||||
|
return result
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,24 +545,40 @@ export default class PocomathInstance {
|
|||||||
if (!imps) {
|
if (!imps) {
|
||||||
throw new SyntaxError(`No implementations for ${name}`)
|
throw new SyntaxError(`No implementations for ${name}`)
|
||||||
}
|
}
|
||||||
const usableEntries = Object.entries(imps).filter(
|
/* Collect the entries we know the types for */
|
||||||
([signature]) => subsetOfKeys(typesOfSignature(signature), this.Types))
|
const usableEntries = []
|
||||||
|
for (const entry of Object.entries(imps)) {
|
||||||
|
let keep = true
|
||||||
|
for (const type of typesOfSignature(entry[0])) {
|
||||||
|
if (type in this.Types) continue
|
||||||
|
const baseType = type.split('<')[0]
|
||||||
|
if (baseType in this.Templates) continue
|
||||||
|
keep = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (keep) usableEntries.push(entry)
|
||||||
|
}
|
||||||
if (usableEntries.length === 0) {
|
if (usableEntries.length === 0) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Every implementation for ${name} uses an undefined type;\n`
|
`Every implementation for ${name} uses an undefined type;\n`
|
||||||
+ ` signatures: ${Object.keys(imps)}`)
|
+ ` signatures: ${Object.keys(imps)}`)
|
||||||
}
|
}
|
||||||
|
/* Initial error checking done; mark this method as being
|
||||||
|
* in the midst of being reassembled
|
||||||
|
*/
|
||||||
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const [rawSignature, behavior] of usableEntries) {
|
for (const [rawSignature, behavior] of usableEntries) {
|
||||||
/* Check if it's an ordinary non-template signature */
|
/* Check if it's an ordinary non-template signature */
|
||||||
let explicit = true
|
let explicit = true
|
||||||
for (const type of typesOfSignature(rawSignature)) {
|
for (const type of typesOfSignature(rawSignature)) {
|
||||||
if (this._templateParam(type)) { // template types need better check
|
for (const word of type.split(/[<>]/)) {
|
||||||
|
if (this._templateParam(word)) {
|
||||||
explicit = false
|
explicit = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (explicit) {
|
if (explicit) {
|
||||||
this._addTFimplementation(tf_imps, rawSignature, behavior)
|
this._addTFimplementation(tf_imps, rawSignature, behavior)
|
||||||
continue
|
continue
|
||||||
@ -485,24 +589,18 @@ export default class PocomathInstance {
|
|||||||
behavior.instantiations = new Set()
|
behavior.instantiations = new Set()
|
||||||
}
|
}
|
||||||
let instantiationSet = new Set()
|
let instantiationSet = new Set()
|
||||||
let trimSignature = rawSignature
|
|
||||||
if (rawSignature.charAt(0) === '!') {
|
|
||||||
trimSignature = trimSignature.slice(1)
|
|
||||||
instantiationSet = this._usedTypes
|
|
||||||
} else {
|
|
||||||
for (const instType of behavior.instantiations) {
|
for (const instType of behavior.instantiations) {
|
||||||
instantiationSet.add(instType)
|
instantiationSet.add(instType)
|
||||||
for (const other of this._priorTypes[instType]) {
|
for (const other of this._priorTypes[instType]) {
|
||||||
instantiationSet.add(other)
|
instantiationSet.add(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
const signature =
|
const signature =
|
||||||
substituteInSig(trimSignature, theTemplateParam, instType)
|
substituteInSig(rawSignature, theTemplateParam, instType)
|
||||||
/* Don't override an explicit implementation: */
|
/* Don't override an explicit implementation: */
|
||||||
if (signature in imps) continue
|
if (signature in imps) continue
|
||||||
const uses = new Set()
|
const uses = new Set()
|
||||||
@ -521,44 +619,170 @@ export default class PocomathInstance {
|
|||||||
innerRefs[dep] = refs[outerName]
|
innerRefs[dep] = refs[outerName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const original = behavior.does(innerRefs)
|
|
||||||
return behavior.does(innerRefs)
|
return behavior.does(innerRefs)
|
||||||
}
|
}
|
||||||
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
this._addTFimplementation(
|
||||||
|
tf_imps, signature, {uses, does: patch})
|
||||||
}
|
}
|
||||||
/* Now add the catchall signature */
|
/* Now add the catchall signature */
|
||||||
|
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 = substituteInSig(
|
const signature = substituteInSig(
|
||||||
trimSignature, theTemplateParam, 'any')
|
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:
|
||||||
*/
|
*/
|
||||||
const argTypes = trimSignature.split(',')
|
const parTypes = rawSignature.split(',')
|
||||||
let exemplar = -1
|
const restParam = (parTypes[parTypes.length-1].slice(0,3) === '...')
|
||||||
for (let i = 0; i < argTypes.length; ++i) {
|
const topTyper = entity => this.typeOf(entity)
|
||||||
const argType = argTypes[i].trim()
|
const inferences = parTypes.map(
|
||||||
if (argType === theTemplateParam) {
|
type => generateTypeExtractor(
|
||||||
exemplar = i
|
type,
|
||||||
break
|
theTemplateParam,
|
||||||
}
|
topTyper,
|
||||||
}
|
this.joinTypes.bind(this),
|
||||||
if (exemplar < 0) {
|
this.Templates))
|
||||||
|
if (inferences.every(x => !x)) { // all false
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Cannot find template parameter in ${rawSignature}`)
|
`Cannot find template parameter in ${rawSignature}`)
|
||||||
}
|
}
|
||||||
|
/* And eliminate template parameters from the dependencies */
|
||||||
|
const simplifiedUses = {}
|
||||||
|
for (const dep of behavior.uses) {
|
||||||
|
let [func, needsig] = dep.split(/[()]/)
|
||||||
|
if (needsig) {
|
||||||
|
const subsig = substituteInSig(needsig, theTemplateParam, '')
|
||||||
|
if (subsig === needsig) {
|
||||||
|
simplifiedUses[dep] = dep
|
||||||
|
} else {
|
||||||
|
simplifiedUses[dep] = func
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
simplifiedUses[dep] = dep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Now build the catchall implementation */
|
||||||
const self = this
|
const self = this
|
||||||
const patch = (refs) => (...args) => {
|
const patch = (refs) => (...args) => {
|
||||||
const example = args[exemplar]
|
/* We unbundle the rest arg if there is one */
|
||||||
const instantiateFor = self.typeOf(example)
|
const regLength = args.length - 1
|
||||||
|
if (restParam) {
|
||||||
|
const restArgs = args.pop()
|
||||||
|
args = args.concat(restArgs)
|
||||||
|
}
|
||||||
|
/* Now infer the type we actually should have been called for */
|
||||||
|
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(', '))
|
||||||
|
}
|
||||||
|
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') {
|
||||||
|
usedConversions = true
|
||||||
|
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
||||||
|
if (instantiateFor === 'any') {
|
||||||
|
throw TypeError(
|
||||||
|
`In call to ${name}, no type unifies arguments `
|
||||||
|
+ args.toString() + '; of types ' + argTypes.toString()
|
||||||
|
+ '; note each consecutive pair must unify to a '
|
||||||
|
+ 'supertype of at least one of them')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Generate the list of actual wanted types */
|
||||||
|
const wantTypes = parTypes.map(type => substituteInSig(
|
||||||
|
type, theTemplateParam, instantiateFor))
|
||||||
|
/* 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 = substituteInSig(
|
||||||
|
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
|
||||||
|
*/
|
||||||
refs[theTemplateParam] = instantiateFor
|
refs[theTemplateParam] = instantiateFor
|
||||||
behavior.instantiations.add(instantiateFor)
|
behavior.instantiations.add(instantiateFor)
|
||||||
self._invalidate(name)
|
self._invalidate(name)
|
||||||
// And for now, we have to rely on the "any" implementation. Hope
|
// And update refs because we now know the type we're instantiating
|
||||||
// it matches the instantiated one!
|
// for:
|
||||||
return behavior.does(refs)(...args)
|
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 = substituteInSig(
|
||||||
|
needsig, theTemplateParam, instantiateFor)
|
||||||
|
let resname = simplifiedDep
|
||||||
|
if (resname === 'self') resname = name
|
||||||
|
innerRefs[dep] = self._pocoresolve(resname, subsig)
|
||||||
|
} else {
|
||||||
|
innerRefs[dep] = refs[simplifiedDep]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally ready to make the call.
|
||||||
|
return behavior.does(innerRefs)(...args)
|
||||||
|
}
|
||||||
|
// The actual uses value needs to be a set:
|
||||||
|
const outerUses = new Set(Object.values(simplifiedUses))
|
||||||
this._addTFimplementation(
|
this._addTFimplementation(
|
||||||
tf_imps, signature, {uses: behavior.uses, does: patch})
|
tf_imps, signature, {uses: outerUses, does: patch})
|
||||||
}
|
}
|
||||||
|
this._correctPartialSelfRefs(tf_imps)
|
||||||
const tf = this._typed(name, tf_imps)
|
const tf = this._typed(name, tf_imps)
|
||||||
Object.defineProperty(this, name, {configurable: true, value: tf})
|
Object.defineProperty(this, name, {configurable: true, value: tf})
|
||||||
return tf
|
return tf
|
||||||
@ -579,9 +803,17 @@ export default class PocomathInstance {
|
|||||||
let part_self_references = []
|
let part_self_references = []
|
||||||
for (const dep of uses) {
|
for (const dep of uses) {
|
||||||
let [func, needsig] = dep.split(/[()]/)
|
let [func, needsig] = dep.split(/[()]/)
|
||||||
const needTypes = needsig ? typesOfSignature(needsig) : new Set()
|
/* Safety check that can perhaps be removed:
|
||||||
/* For now, punt on template parameters */
|
* Verify that the desired signature has been fully grounded:
|
||||||
if (needTypes.has(theTemplateParam)) needsig = ''
|
*/
|
||||||
|
if (needsig) {
|
||||||
|
const trysig = substituteInSig(needsig, theTemplateParam, '')
|
||||||
|
if (trysig !== needsig) {
|
||||||
|
throw new Error(
|
||||||
|
'Attempt to add a template implementation: ' +
|
||||||
|
`${signature} with dependency ${dep}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (func === 'self') {
|
if (func === 'self') {
|
||||||
if (needsig) {
|
if (needsig) {
|
||||||
if (full_self_referential) {
|
if (full_self_referential) {
|
||||||
@ -614,7 +846,7 @@ export default class PocomathInstance {
|
|||||||
// can bundle up func, and grab its signature if need be
|
// can bundle up func, and grab its signature if need be
|
||||||
let destination = this[func]
|
let destination = this[func]
|
||||||
if (needsig) {
|
if (needsig) {
|
||||||
destination = this._typed.find(destination, needsig)
|
destination = this._pocoresolve(func, needsig)
|
||||||
}
|
}
|
||||||
refs[dep] = destination
|
refs[dep] = destination
|
||||||
}
|
}
|
||||||
@ -628,16 +860,215 @@ export default class PocomathInstance {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (part_self_references.length) {
|
if (part_self_references.length) {
|
||||||
imps[signature] = this._typed.referTo(
|
/* There is an obstruction here. The list part_self_references
|
||||||
...part_self_references, (...impls) => {
|
* might contain a signature that requires conversion for self to
|
||||||
|
* handle. But I advocated this not be allowed in typed.referTo, which
|
||||||
|
* made sense for human-written functions, but is unfortunate now.
|
||||||
|
* So we have to defer creating these and correct them later, at
|
||||||
|
* least until we can add an option to typed-function.
|
||||||
|
*/
|
||||||
|
imps[signature] = {
|
||||||
|
deferred: true,
|
||||||
|
builtRefs: refs,
|
||||||
|
sigDoes: does,
|
||||||
|
psr: part_self_references
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imps[signature] = does(refs)
|
||||||
|
}
|
||||||
|
|
||||||
|
_correctPartialSelfRefs(imps) {
|
||||||
|
for (const aSignature in imps) {
|
||||||
|
if (!(imps[aSignature].deferred)) continue
|
||||||
|
const part_self_references = imps[aSignature].psr
|
||||||
|
const corrected_self_references = []
|
||||||
|
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)
|
||||||
|
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(imps, neededSig)
|
||||||
|
if (foundSig) {
|
||||||
|
corrected_self_references.push(foundSig)
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Implement inexact self-reference in typed-function for '
|
||||||
|
+ neededSig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const refs = imps[aSignature].builtRefs
|
||||||
|
const does = imps[aSignature].sigDoes
|
||||||
|
imps[aSignature] = this._typed.referTo(
|
||||||
|
...corrected_self_references, (...impls) => {
|
||||||
for (let i = 0; i < part_self_references.length; ++i) {
|
for (let i = 0; i < part_self_references.length; ++i) {
|
||||||
refs[`self(${part_self_references[i]})`] = impls[i]
|
refs[`self(${part_self_references[i]})`] = impls[i]
|
||||||
}
|
}
|
||||||
return does(refs)
|
return does(refs)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return
|
|
||||||
}
|
|
||||||
imps[signature] = does(refs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This function analyzes the template and makes sure the
|
||||||
|
* instantiations of it for type and all prior types of type are present
|
||||||
|
* in the instance.
|
||||||
|
*/
|
||||||
|
_ensureTemplateTypes(template, type) {
|
||||||
|
let [base, arg] = template.split('<', 2)
|
||||||
|
arg = arg.slice(0,-1)
|
||||||
|
if (!arg) {
|
||||||
|
throw new Error(
|
||||||
|
'Implementation error in _ensureTemplateTypes', template, type)
|
||||||
|
}
|
||||||
|
let instantiations
|
||||||
|
if (this._templateParam(arg)) { // 1st-level template
|
||||||
|
instantiations = new Set(this._priorTypes[type])
|
||||||
|
instantiations.add(type)
|
||||||
|
} else { // nested template
|
||||||
|
instantiations = this._ensureTemplateTypes(arg, type)
|
||||||
|
}
|
||||||
|
const resultingTypes = new Set()
|
||||||
|
for (const iType of instantiations) {
|
||||||
|
const resultType = this._maybeAddTemplateType(base, iType)
|
||||||
|
if (resultType) resultingTypes.add(resultType)
|
||||||
|
}
|
||||||
|
return resultingTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Maybe add the instantiation of template type base with argument tyoe
|
||||||
|
* instantiator to the Types of this instance, if it hasn't happened already.
|
||||||
|
* Returns the name of the type if added, false otherwise.
|
||||||
|
*/
|
||||||
|
_maybeAddTemplateType(base, instantiator) {
|
||||||
|
const wantsType = `${base}<${instantiator}>`
|
||||||
|
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 maybeFrom = {}
|
||||||
|
const template = this.Templates[base].spec
|
||||||
|
if (!template) {
|
||||||
|
throw new Error(
|
||||||
|
`Implementor error in _maybeAddTemplateType ${base} ${instantiator}`)
|
||||||
|
}
|
||||||
|
const instantiatorSpec = this.Types[instantiator]
|
||||||
|
let beforeTypes = []
|
||||||
|
if (instantiatorSpec.before) {
|
||||||
|
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
||||||
|
}
|
||||||
|
if (template.before) {
|
||||||
|
for (const beforeTmpl of template.before) {
|
||||||
|
beforeTypes.push(
|
||||||
|
substituteInSig(beforeTmpl, theTemplateParam, instantiator))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (beforeTypes.length > 0) {
|
||||||
|
newTypeSpec.before = beforeTypes
|
||||||
|
}
|
||||||
|
newTypeSpec.test = template.test(this._typeTests[instantiator])
|
||||||
|
if (template.from) {
|
||||||
|
for (let source in template.from) {
|
||||||
|
const instSource = substituteInSig(
|
||||||
|
source, theTemplateParam, instantiator)
|
||||||
|
let usesFromParam = false
|
||||||
|
for (const word of instSource.split(/[<>]/)) {
|
||||||
|
if (word === templateFromParam) {
|
||||||
|
usesFromParam = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (usesFromParam) {
|
||||||
|
for (const iFrom in instantiatorSpec.from) {
|
||||||
|
const finalSource = substituteInSig(
|
||||||
|
instSource, templateFromParam, iFrom)
|
||||||
|
maybeFrom[finalSource] = template.from[source](
|
||||||
|
instantiatorSpec.from[iFrom])
|
||||||
|
}
|
||||||
|
// Assuming all templates are covariant here, I guess...
|
||||||
|
for (const subType of this._subtypes[instantiator]) {
|
||||||
|
const finalSource = substituteInSig(
|
||||||
|
instSource, templateFromParam, subType)
|
||||||
|
maybeFrom[finalSource] = template.from[source](x => x)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maybeFrom[instSource] = template.from[source]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(maybeFrom).length > 0) {
|
||||||
|
newTypeSpec.from = maybeFrom
|
||||||
|
}
|
||||||
|
this.installType(wantsType, newTypeSpec)
|
||||||
|
return wantsType
|
||||||
|
}
|
||||||
|
|
||||||
|
_findSubtypeImpl(imps, neededSig) {
|
||||||
|
if (neededSig in imps) return neededSig
|
||||||
|
let foundSig = false
|
||||||
|
const typeList = typeListOfSignature(neededSig)
|
||||||
|
for (const otherSig in imps) {
|
||||||
|
const otherTypeList = typeListOfSignature(otherSig)
|
||||||
|
if (typeList.length !== otherTypeList.length) continue
|
||||||
|
let allMatch = true
|
||||||
|
for (let k = 0; k < typeList.length; ++k) {
|
||||||
|
let myType = typeList[k]
|
||||||
|
let otherType = otherTypeList[k]
|
||||||
|
if (otherType === theTemplateParam) {
|
||||||
|
otherTypeList[k] = 'ground'
|
||||||
|
otherType = 'ground'
|
||||||
|
}
|
||||||
|
if (otherType === '...T') {
|
||||||
|
otherTypeList[k] = '...ground'
|
||||||
|
otherType = 'ground'
|
||||||
|
}
|
||||||
|
const adjustedOtherType = otherType.replaceAll(
|
||||||
|
`<${theTemplateParam}>`, '')
|
||||||
|
if (adjustedOtherType !== otherType) {
|
||||||
|
otherTypeList[k] = adjustedOtherType
|
||||||
|
otherType = adjustedOtherType
|
||||||
|
}
|
||||||
|
if (myType.slice(0,3) === '...') myType = myType.slice(3)
|
||||||
|
if (otherType.slice(0,3) === '...') otherType = otherType.slice(3)
|
||||||
|
if (otherType === 'any') continue
|
||||||
|
if (otherType === 'ground') continue
|
||||||
|
if (!(otherType in this.Types)) {
|
||||||
|
allMatch = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (myType === otherType
|
||||||
|
|| this._subtypes[otherType].has(myType)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allMatch = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (allMatch) {
|
||||||
|
foundSig = otherTypeList.join(',')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundSig
|
||||||
|
}
|
||||||
|
|
||||||
|
_pocoresolve(name, sig) {
|
||||||
|
const typedfunc = this[name]
|
||||||
|
let result = undefined
|
||||||
|
try {
|
||||||
|
result = this._typed.find(typedfunc, sig, {exact: true})
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
if (result) return result
|
||||||
|
const foundsig = this._findSubtypeImpl(this._imps[name], sig)
|
||||||
|
if (foundsig) return this._typed.find(typedfunc, foundsig)
|
||||||
|
return this._typed.find(typedfunc, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
/* Call this with an empty Set object S, and it returns an entity E
|
|
||||||
* from which properties can be extracted, and at any time S will
|
|
||||||
* contain all of the property names that have been extracted from E.
|
|
||||||
*/
|
|
||||||
export default function dependencyExtractor(destinationSet) {
|
|
||||||
return new Proxy({}, {
|
|
||||||
get: (target, property) => {
|
|
||||||
destinationSet.add(property)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
41
src/core/extractors.mjs
Normal file
41
src/core/extractors.mjs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* Call this with an empty Set object S, and it returns an entity E
|
||||||
|
* from which properties can be extracted, and at any time S will
|
||||||
|
* contain all of the property names that have been extracted from E.
|
||||||
|
*/
|
||||||
|
export function dependencyExtractor(destinationSet) {
|
||||||
|
return new Proxy({}, {
|
||||||
|
get: (target, property) => {
|
||||||
|
destinationSet.add(property)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Given a (template) type name, what the template parameter is,
|
||||||
|
* a top level typer, and a library of templates,
|
||||||
|
* produces a function that will extract the instantantion type from an
|
||||||
|
* instance. Currently relies heavily on there being only unary templates.
|
||||||
|
*
|
||||||
|
* We should really be using the typed-function parser to do the
|
||||||
|
* manipulations below, but at the moment we don't have access.
|
||||||
|
*/
|
||||||
|
export function generateTypeExtractor(
|
||||||
|
type, param, topTyper, typeJoiner, templates)
|
||||||
|
{
|
||||||
|
type = type.trim()
|
||||||
|
if (type.slice(0,3) === '...') {
|
||||||
|
type = type.slice(3).trim()
|
||||||
|
}
|
||||||
|
if (type === param) return topTyper
|
||||||
|
if (!(type.includes('<'))) return false // no template type to extract
|
||||||
|
const base = type.split('<',1)[0]
|
||||||
|
if (!(base in templates)) return false // unknown template
|
||||||
|
const arg = type.slice(base.length+1, -1)
|
||||||
|
const argExtractor = generateTypeExtractor(
|
||||||
|
arg, param, topTyper, typeJoiner, templates)
|
||||||
|
if (!argExtractor) return false
|
||||||
|
return templates[base].spec.infer({
|
||||||
|
typeOf: argExtractor,
|
||||||
|
joinTypes: typeJoiner
|
||||||
|
})
|
||||||
|
}
|
@ -6,6 +6,11 @@ export function subsetOfKeys(set, obj) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns a list of the types mentioned in a typed-function signature */
|
||||||
|
export function typeListOfSignature(signature) {
|
||||||
|
return signature.split(',').map(s => s.trim())
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns a set of all of the types mentioned in a typed-function signature */
|
/* Returns a set of all of the types mentioned in a typed-function signature */
|
||||||
export function typesOfSignature(signature) {
|
export function typesOfSignature(signature) {
|
||||||
return new Set(signature.split(/[^\w\d]/).filter(s => s.length))
|
return new Set(signature.split(/[^\w\d]/).filter(s => s.length))
|
||||||
|
@ -3,6 +3,8 @@ import {reducingOperation} from './reducingOperation.mjs'
|
|||||||
export * from './Types/generic.mjs'
|
export * from './Types/generic.mjs'
|
||||||
|
|
||||||
export const add = reducingOperation
|
export const add = reducingOperation
|
||||||
|
export const gcd = reducingOperation
|
||||||
|
export {identity} from './identity.mjs'
|
||||||
export {lcm} from './lcm.mjs'
|
export {lcm} from './lcm.mjs'
|
||||||
export {mean} from './mean.mjs'
|
export {mean} from './mean.mjs'
|
||||||
export {mod} from './mod.mjs'
|
export {mod} from './mod.mjs'
|
||||||
|
3
src/generic/identity.mjs
Normal file
3
src/generic/identity.mjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function identity(x) {
|
||||||
|
return x
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import {reducingOperation} from './reducingOperation.mjs'
|
||||||
|
|
||||||
export const lcm = {
|
export const lcm = {
|
||||||
'T,T': ({
|
'T,T': ({
|
||||||
'multiply(T,T)': multT,
|
'multiply(T,T)': multT,
|
||||||
@ -5,3 +7,4 @@ export const lcm = {
|
|||||||
'gcd(T,T)': gcdT
|
'gcd(T,T)': gcdT
|
||||||
}) => (a,b) => multT(quotT(a, gcdT(a,b)), b)
|
}) => (a,b) => multT(quotT(a, gcdT(a,b)), b)
|
||||||
}
|
}
|
||||||
|
Object.assign(lcm, reducingOperation)
|
||||||
|
@ -7,14 +7,27 @@ export const isZero = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const equal = {
|
export const equal = {
|
||||||
'!T,T': ({
|
'any,any': ({equalTT, joinTypes, Templates, typeOf}) => (x,y) => {
|
||||||
|
const resultant = joinTypes([typeOf(x), typeOf(y)], 'convert')
|
||||||
|
if (resultant === 'any' || resultant in Templates) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalTT(x,y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const equalTT = {
|
||||||
|
'T,T': ({
|
||||||
'compare(T,T)': cmp,
|
'compare(T,T)': cmp,
|
||||||
'isZero(T)': isZ
|
'isZero(T)': isZ
|
||||||
}) => (x,y) => isZ(cmp(x,y))
|
}) => (x,y) => isZ(cmp(x,y)),
|
||||||
|
// If templates were native to typed-function, we should be able to
|
||||||
|
// do something like:
|
||||||
|
// 'any,any': () => () => false // should only be hit for different types
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unequal = {
|
export const unequal = {
|
||||||
'T,T': ({'equal(T.T)': eq}) => (x,y) => !(eq(x,y))
|
'any,any': ({equal}) => (x,y) => !(equal(x,y))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const larger = {
|
export const larger = {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import gcdType from '../generic/gcdType.mjs'
|
import gcdType from '../generic/gcdType.mjs'
|
||||||
|
import {identity} from '../generic/identity.mjs'
|
||||||
|
|
||||||
export * from './Types/number.mjs'
|
export * from './Types/number.mjs'
|
||||||
|
|
||||||
export {abs} from './abs.mjs'
|
export {abs} from './abs.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {compare} from './compare.mjs'
|
export {compare} from './compare.mjs'
|
||||||
|
export const conjugate = {number: () => identity}
|
||||||
export const gcd = gcdType('NumInt')
|
export const gcd = gcdType('NumInt')
|
||||||
export {invert} from './invert.mjs'
|
export {invert} from './invert.mjs'
|
||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
|
@ -12,7 +12,7 @@ export const floor = {
|
|||||||
// entry with type `bigint|NumInt|GaussianInteger` because they couldn't
|
// entry with type `bigint|NumInt|GaussianInteger` because they couldn't
|
||||||
// be separately activated then
|
// be separately activated then
|
||||||
|
|
||||||
number: ({'equal(number,number)': eq}) => n => {
|
number: ({'equalTT(number,number)': eq}) => n => {
|
||||||
if (eq(n, Math.round(n))) return Math.round(n)
|
if (eq(n, Math.round(n))) return Math.round(n)
|
||||||
return Math.floor(n)
|
return Math.floor(n)
|
||||||
},
|
},
|
||||||
|
@ -3,10 +3,11 @@ import PocomathInstance from './core/PocomathInstance.mjs'
|
|||||||
import * as numbers from './number/native.mjs'
|
import * as numbers from './number/native.mjs'
|
||||||
import * as bigints from './bigint/native.mjs'
|
import * as bigints from './bigint/native.mjs'
|
||||||
import * as complex from './complex/native.mjs'
|
import * as complex from './complex/native.mjs'
|
||||||
|
import * as tuple from './tuple/native.mjs'
|
||||||
import * as generic from './generic/all.mjs'
|
import * as generic from './generic/all.mjs'
|
||||||
import * as ops from './ops/all.mjs'
|
import * as ops from './ops/all.mjs'
|
||||||
|
|
||||||
const math = PocomathInstance.merge(
|
const math = PocomathInstance.merge(
|
||||||
'math', numbers, bigints, complex, generic, ops)
|
'math', numbers, bigints, complex, tuple, generic, ops)
|
||||||
|
|
||||||
export default math
|
export default math
|
||||||
|
80
src/tuple/Types/Tuple.mjs
Normal file
80
src/tuple/Types/Tuple.mjs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/* A template type representing a homeogeneous tuple of elements */
|
||||||
|
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>
|
||||||
|
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>:
|
||||||
|
from: {
|
||||||
|
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
|
||||||
|
// Here since there is no U it's a straight conversion:
|
||||||
|
T: t => ({elts: [t]}), // singleton promotion
|
||||||
|
// Whereas the following will let you go directly from an element
|
||||||
|
// convertible to T to a singleton Tuple<T>. Not sure if we really
|
||||||
|
// want that, but we'll try it just for kicks.
|
||||||
|
U: convert => u => ({elts: [convert(u)]})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Tuple.promoteUnary = {
|
||||||
|
'Tuple<T>': ({'self(T)': me, tuple}) => t => tuple(...(t.elts.map(me)))
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple.promoteBinaryUnary = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, 'self(T)': meU, tuple}) => (s,t) => {
|
||||||
|
let i = -1
|
||||||
|
let result = []
|
||||||
|
while (true) {
|
||||||
|
i += 1
|
||||||
|
if (i < s.elts.length) {
|
||||||
|
if (i < t.elts.length) result.push(meB(s.elts[i], t.elts[i]))
|
||||||
|
else result.push(meU(s.elts[i]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (i < t.elts.length) result.push(meU(t.elts[i]))
|
||||||
|
else break
|
||||||
|
}
|
||||||
|
return tuple(...result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple.promoteBinary = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, tuple}) => (s,t) => {
|
||||||
|
const lim = Math.max(s.elts.length, t.elts.length)
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < lim; ++i) {
|
||||||
|
result.push(meB(s.elts[i], t.elts[i]))
|
||||||
|
}
|
||||||
|
return tuple(...result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple.promoteBinaryStrict = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, tuple}) => (s,t) => {
|
||||||
|
if (s.elts.length !== t.elts.length) {
|
||||||
|
throw new RangeError('Tuple length mismatch') // get name of self ??
|
||||||
|
}
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < s.elts.length; ++i) {
|
||||||
|
result.push(meB(s.elts[i], t.elts[i]))
|
||||||
|
}
|
||||||
|
return tuple(...result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Tuple}
|
11
src/tuple/equalTT.mjs
Normal file
11
src/tuple/equalTT.mjs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export * from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const equalTT = {
|
||||||
|
'Tuple<T>,Tuple<T>': ({'self(T,T)': me, 'length(Tuple)': len}) => (s,t) => {
|
||||||
|
if (len(s) !== len(t)) return false
|
||||||
|
for (let i = 0; i < len(s); ++i) {
|
||||||
|
if (!me(s.elts[i], t.elts[i])) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
8
src/tuple/isZero.mjs
Normal file
8
src/tuple/isZero.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const isZero = {
|
||||||
|
'Tuple<T>': ({'self(T)': me}) => t => t.elts.every(e => me(e))
|
||||||
|
// Note we can't just say `every(me)` above since every invokes its
|
||||||
|
// callback with more arguments, which then violates typed-function's
|
||||||
|
// signature for `me`
|
||||||
|
}
|
3
src/tuple/length.mjs
Normal file
3
src/tuple/length.mjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const length = {Tuple: () => t => t.elts.length}
|
21
src/tuple/native.mjs
Normal file
21
src/tuple/native.mjs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
export const add = Tuple.promoteBinaryUnary
|
||||||
|
export const complex = Tuple.promoteBinaryStrict
|
||||||
|
export const conjugate = Tuple.promoteUnary
|
||||||
|
export const divide = Tuple.promoteBinaryStrict
|
||||||
|
export {equalTT} from './equalTT.mjs'
|
||||||
|
export const invert = Tuple.promoteUnary
|
||||||
|
export {isZero} from './isZero.mjs'
|
||||||
|
export {length} from './length.mjs'
|
||||||
|
export const multiply = Tuple.promoteBinaryUnary
|
||||||
|
export const negate = Tuple.promoteUnary
|
||||||
|
export const one = Tuple.promoteUnary
|
||||||
|
export const quotient = Tuple.promoteBinaryStrict
|
||||||
|
export const roundquotient = Tuple.promoteBinaryStrict
|
||||||
|
export const sqrt = Tuple.promoteUnary
|
||||||
|
export const subtract = Tuple.promoteBinaryStrict
|
||||||
|
export {tuple} from './tuple.mjs'
|
||||||
|
export const zero = Tuple.promoteUnary
|
||||||
|
|
||||||
|
export {Tuple}
|
6
src/tuple/tuple.mjs
Normal file
6
src/tuple/tuple.mjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export {Tuple} from './Types/Tuple.mjs'
|
||||||
|
|
||||||
|
/* The purpose of the template argument is to ensure that all of the args
|
||||||
|
* are convertible to the same type.
|
||||||
|
*/
|
||||||
|
export const tuple = {'...T': () => args => ({elts: args})}
|
@ -103,4 +103,13 @@ describe('The default full pocomath instance "math"', () => {
|
|||||||
assert.strictEqual(math.choose(21n, 2n), 210n)
|
assert.strictEqual(math.choose(21n, 2n), 210n)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('calculates multi-way gcds and lcms', () => {
|
||||||
|
assert.strictEqual(math.gcd(30,105,42), 3)
|
||||||
|
assert.ok(
|
||||||
|
math.associate(
|
||||||
|
math.lcm(
|
||||||
|
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)),
|
||||||
|
math.complex(1n,3n)))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import dependencyExtractor from '../../src/core/dependencyExtractor.mjs'
|
import {dependencyExtractor} from '../../src/core/extractors.mjs'
|
||||||
|
|
||||||
describe('dependencyExtractor', () => {
|
describe('dependencyExtractor', () => {
|
||||||
it('will record the keys of a destructuring function', () => {
|
it('will record the keys of a destructuring function', () => {
|
@ -8,6 +8,7 @@ import * as complex from '../src/complex/all.mjs'
|
|||||||
import * as complexAdd from '../src/complex/add.mjs'
|
import * as complexAdd from '../src/complex/add.mjs'
|
||||||
import * as complexNegate from '../src/complex/negate.mjs'
|
import * as complexNegate from '../src/complex/negate.mjs'
|
||||||
import * as complexComplex from '../src/complex/complex.mjs'
|
import * as complexComplex from '../src/complex/complex.mjs'
|
||||||
|
import * as bigintAdd from '../src/bigint/add.mjs'
|
||||||
import * as concreteSubtract from '../src/generic/subtract.concrete.mjs'
|
import * as concreteSubtract from '../src/generic/subtract.concrete.mjs'
|
||||||
import * as genericSubtract from '../src/generic/subtract.mjs'
|
import * as genericSubtract from '../src/generic/subtract.mjs'
|
||||||
import extendToComplex from '../src/complex/extendToComplex.mjs'
|
import extendToComplex from '../src/complex/extendToComplex.mjs'
|
||||||
@ -17,9 +18,10 @@ describe('A custom instance', () => {
|
|||||||
it("works when partially assembled", () => {
|
it("works when partially assembled", () => {
|
||||||
bw.install(complex)
|
bw.install(complex)
|
||||||
// Not much we can call without any number types:
|
// Not much we can call without any number types:
|
||||||
const i3 = {re: 0, im: 3}
|
assert.deepStrictEqual(bw.complex(undefined, undefined), undefined)
|
||||||
assert.deepStrictEqual(bw.complex(0, 3), i3)
|
assert.deepStrictEqual(
|
||||||
assert.deepStrictEqual(bw.chain(0).complex(3).value, i3)
|
bw.chain(undefined).complex(undefined).value,
|
||||||
|
undefined)
|
||||||
// Don't have a way to negate things, for example:
|
// Don't have a way to negate things, for example:
|
||||||
assert.throws(() => bw.negate(2), TypeError)
|
assert.throws(() => bw.negate(2), TypeError)
|
||||||
})
|
})
|
||||||
@ -33,7 +35,7 @@ describe('A custom instance', () => {
|
|||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
bw.subtract(16, bw.add(3, bw.complex(0,4), 2)),
|
bw.subtract(16, bw.add(3, bw.complex(0,4), 2)),
|
||||||
math.complex(11, -4)) // note both instances coexist
|
math.complex(11, -4)) // note both instances coexist
|
||||||
assert.deepStrictEqual(bw.negate(math.complex(3, '8')).im, -8)
|
assert.deepStrictEqual(bw.negate(bw.complex(3, '8')).im, -8)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can be assembled piecemeal", () => {
|
it("can be assembled piecemeal", () => {
|
||||||
@ -112,4 +114,30 @@ describe('A custom instance', () => {
|
|||||||
math.complex(1n, -3n))
|
math.complex(1n, -3n))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("instantiates templates correctly", () => {
|
||||||
|
const inst = new PocomathInstance('InstantiateTemplates')
|
||||||
|
inst.install(numberAdd)
|
||||||
|
inst.install({typeMerge: {'T,T': ({T}) => (t,u) => 'Merge to ' + T }})
|
||||||
|
assert.strictEqual(inst.typeMerge(7,6.28), 'Merge to number')
|
||||||
|
assert.strictEqual(inst.typeMerge(7,6), 'Merge to NumInt')
|
||||||
|
assert.strictEqual(inst.typeMerge(7.35,6), 'Merge to number')
|
||||||
|
inst.install(complexAdd)
|
||||||
|
inst.install(complexComplex)
|
||||||
|
inst.install(bigintAdd)
|
||||||
|
assert.strictEqual(
|
||||||
|
inst.typeMerge(6n, inst.complex(3n, 2n)),
|
||||||
|
'Merge to GaussianInteger')
|
||||||
|
assert.strictEqual(
|
||||||
|
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
||||||
|
'Merge to Complex')
|
||||||
|
// The following is the current behavior, since 3 converts to 3+0i
|
||||||
|
// which is technically the same Complex type as 3n+0ni.
|
||||||
|
// This should clear up when Complex is templatized
|
||||||
|
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)
|
||||||
|
assert.throws(() => inst.typeMerge(3, undefined), TypeError)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
114
test/tuple/_native.mjs
Normal file
114
test/tuple/_native.mjs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import assert from 'assert'
|
||||||
|
import math from '../../src/pocomath.mjs'
|
||||||
|
|
||||||
|
describe('tuple', () => {
|
||||||
|
it('can be created and provide its length', () => {
|
||||||
|
assert.strictEqual(math.length(math.tuple(3, 5.2, 2)), 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
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)]})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can be tested for zero and equality', () => {
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0,1)), false)
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0n,0n,0n,0n)), true)
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0,0.001,0)), false)
|
||||||
|
assert.deepStrictEqual(math.complex(0,0), {re: 0, im:0})
|
||||||
|
assert.strictEqual(math.isZero(math.tuple(0,math.complex(0,0))), true)
|
||||||
|
assert.strictEqual(
|
||||||
|
math.equal(
|
||||||
|
math.tuple(0,math.complex(0,0.1)),
|
||||||
|
math.complex(math.tuple(0,0), math.tuple(0,0.1))),
|
||||||
|
true)
|
||||||
|
assert.strictEqual(
|
||||||
|
math.equal(math.tuple(3n,2n), math.tuple(3,2)),
|
||||||
|
false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports addition', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(math.tuple(3,4,5), math.tuple(2,1,0)),
|
||||||
|
math.tuple(5,5,5))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(math.tuple(3.25,4.5,5), math.tuple(3,3)),
|
||||||
|
math.tuple(6.25,7.5,5))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(math.tuple(math.complex(2,3), 7), math.tuple(4, 5, 6)),
|
||||||
|
math.tuple(math.complex(6,3), math.complex(12), math.complex(6)))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(math.tuple(5,6), 7),
|
||||||
|
math.tuple(12,6))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(math.tuple(math.complex(5,4),6), 7),
|
||||||
|
math.tuple(math.complex(12,4),math.complex(6)))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports subtraction', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.subtract(math.tuple(3n,4n,5n), math.tuple(2n,1n,0n)),
|
||||||
|
math.tuple(1n,3n,5n))
|
||||||
|
assert.throws(
|
||||||
|
() => math.subtract(math.tuple(5,6), math.tuple(7)),
|
||||||
|
/RangeError/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('makes a tuple of complex and conjugates it', () => {
|
||||||
|
const complexTuple = math.tuple(
|
||||||
|
math.complex(3,1), math.complex(4,2.2), math.complex(5,3))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.complex(math.tuple(3,4,5), math.tuple(1,2.2,3)),
|
||||||
|
complexTuple)
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.conjugate(complexTuple),
|
||||||
|
math.tuple(math.complex(3,-1), math.complex(4,-2.2), math.complex(5,-3)))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports division', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.divide(math.tuple(3,4,5),math.tuple(1,2,2)),
|
||||||
|
math.tuple(3,2,2.5))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports multiplication', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.multiply(math.tuple(3,4,5), math.tuple(1,2,2)),
|
||||||
|
math.tuple(3,8,10))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports one and zero', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.one(math.tuple(2n,3n,0n)),
|
||||||
|
math.tuple(1n,1n,1n))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.zero(math.tuple(math.complex(5,2), 3.4)),
|
||||||
|
math.tuple(math.complex(0), math.complex(0)))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports quotient and roundquotient', () => {
|
||||||
|
const bigTuple = math.tuple(1n,2n,3n,4n,5n)
|
||||||
|
const bigOnes = math.one(bigTuple)
|
||||||
|
const threes = math.add(bigOnes, bigOnes, bigOnes)
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.quotient(bigTuple, threes),
|
||||||
|
math.tuple(0n, 0n, 1n, 1n, 1n))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.roundquotient(bigTuple, threes),
|
||||||
|
math.tuple(0n, 1n, 1n, 1n, 2n))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports sqrt', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.sqrt(math.tuple(4,-4,2.25)),
|
||||||
|
math.tuple(2, math.complex(0,2), 1.5))
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user