feat: Template types #45
@ -1,9 +1,11 @@
|
||||
import gcdType from '../generic/gcdType.mjs'
|
||||
import {identity} from '../generic/identity.mjs'
|
||||
|
||||
export * from './Types/bigint.mjs'
|
||||
|
||||
export {add} from './add.mjs'
|
||||
export {compare} from './compare.mjs'
|
||||
export const conjugate = {bigint: () => identity}
|
||||
export {divide} from './divide.mjs'
|
||||
export const gcd = gcdType('bigint')
|
||||
export {isZero} from './isZero.mjs'
|
||||
|
@ -7,16 +7,16 @@ export const add = {
|
||||
*/
|
||||
'Complex,number': ({
|
||||
'self(number,number)': addNum,
|
||||
'complex(any,any)': cplx
|
||||
'complex(number,number)': cplx
|
||||
}) => (z,x) => cplx(addNum(z.re, x), z.im),
|
||||
|
||||
'Complex,bigint': ({
|
||||
'self(bigint,bigint)': addBigInt,
|
||||
'complex(any,any)': cplx
|
||||
'complex(bigint,bigint)': cplx
|
||||
}) => (z,x) => cplx(addBigInt(z.re, x), z.im),
|
||||
|
||||
'Complex,Complex': ({
|
||||
self,
|
||||
'complex(any,any)': cplx
|
||||
}) => (w,z) => cplx(self(w.re, z.re), self(w.im, z.im))
|
||||
complex
|
||||
}) => (w,z) => complex(self(w.re, z.re), self(w.im, z.im))
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ export * from './Types/Complex.mjs'
|
||||
export const associate = {
|
||||
'Complex,Complex': ({
|
||||
'multiply(Complex,Complex)': times,
|
||||
'equal(Complex,Complex)': eq,
|
||||
'equalTT(Complex,Complex)': eq,
|
||||
zero,
|
||||
one,
|
||||
complex,
|
||||
|
@ -9,7 +9,8 @@ export const complex = {
|
||||
'undefined': () => u => u,
|
||||
'undefined,any': () => (u, y) => 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 */
|
||||
Complex: () => z => z
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const equal = {
|
||||
export const equalTT = {
|
||||
'Complex,number': ({
|
||||
'isZero(number)': isZ,
|
||||
'self(number,number)': eqNum
|
@ -6,7 +6,7 @@ export {add} from './add.mjs'
|
||||
export {associate} from './associate.mjs'
|
||||
export {complex} from './complex.mjs'
|
||||
export {conjugate} from './conjugate.mjs'
|
||||
export {equal} from './equal.mjs'
|
||||
export {equalTT} from './equalTT.mjs'
|
||||
export {gcd} from './gcd.mjs'
|
||||
export {invert} from './invert.mjs'
|
||||
export {isZero} from './isZero.mjs'
|
||||
|
@ -2,7 +2,7 @@
|
||||
import typed from 'typed-function'
|
||||
import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
|
||||
import {makeChain} from './Chain.mjs'
|
||||
import {typesOfSignature, subsetOfKeys} from './utils.mjs'
|
||||
import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs'
|
||||
|
||||
const anySpec = {} // fixed dummy specification of 'any' type
|
||||
|
||||
@ -30,6 +30,8 @@ export default class PocomathInstance {
|
||||
'installType',
|
||||
'joinTypes',
|
||||
'name',
|
||||
'self',
|
||||
'Templates',
|
||||
'typeOf',
|
||||
'Types',
|
||||
'undefinedTypes'
|
||||
@ -41,13 +43,17 @@ export default class PocomathInstance {
|
||||
this._affects = {}
|
||||
this._typed = typed.create()
|
||||
this._typed.clear()
|
||||
this._typed.addTypes([{name: 'ground', test: () => true}])
|
||||
/* List of types installed in the instance. We start with just dummies
|
||||
* for the 'any' type and for type parameters:
|
||||
*/
|
||||
this.Types = {any: anySpec}
|
||||
this.Types[theTemplateParam] = anySpec
|
||||
this.Types.ground = anySpec
|
||||
// All the template types that have been defined
|
||||
this._templateTypes = {}
|
||||
this.Templates = {}
|
||||
// The actual type testing functions
|
||||
this._typeTests = {}
|
||||
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
|
||||
* match in typed-function's dispatch algorithm before the given type.
|
||||
@ -56,8 +62,8 @@ export default class PocomathInstance {
|
||||
* might match.
|
||||
*/
|
||||
this._priorTypes = {}
|
||||
this._usedTypes = new Set() // all types that have occurred in a signature
|
||||
this._doomed = new Set() // for detecting circular reference
|
||||
this._seenTypes = new Set() // all types that have occurred in a signature
|
||||
this._invalid = new Set() // methods that are currently invalid
|
||||
this._config = {predictable: false, epsilon: 1e-12}
|
||||
const self = this
|
||||
this.config = new Proxy(this._config, {
|
||||
@ -72,6 +78,12 @@ export default class PocomathInstance {
|
||||
})
|
||||
this._plainFunctions = new Set() // the names of the plain functions
|
||||
this._chainRepository = {} // place to store chainified functions
|
||||
|
||||
this._installFunctions({
|
||||
typeOf: {ground: {uses: new Set(), does: () => () => 'any'}}
|
||||
})
|
||||
|
||||
this.joinTypes = this.joinTypes.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,10 +193,10 @@ export default class PocomathInstance {
|
||||
|
||||
_installInstance(other) {
|
||||
for (const [type, spec] of Object.entries(other.Types)) {
|
||||
if (type === 'any' || this._templateParam(type)) continue
|
||||
if (spec === anySpec) continue
|
||||
this.installType(type, spec)
|
||||
}
|
||||
for (const [base, info] of Object.entries(other._templateTypes)) {
|
||||
for (const [base, info] of Object.entries(other.Templates)) {
|
||||
this._installTemplateType(info.type, info.spec)
|
||||
}
|
||||
const migrateImps = {}
|
||||
@ -290,7 +302,7 @@ export default class PocomathInstance {
|
||||
}
|
||||
let beforeType = spec.refines
|
||||
if (!beforeType) {
|
||||
beforeType = 'any'
|
||||
beforeType = 'ground'
|
||||
for (const other of spec.before || []) {
|
||||
if (other in this.Types) {
|
||||
beforeType = other
|
||||
@ -303,17 +315,14 @@ export default class PocomathInstance {
|
||||
const supertypeTest = this.Types[spec.refines].test
|
||||
testFn = entity => supertypeTest(entity) && spec.test(entity)
|
||||
}
|
||||
this._typeTests[type] = testFn
|
||||
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
||||
this.Types[type] = spec
|
||||
this._subtypes[type] = new Set()
|
||||
this._priorTypes[type] = new Set()
|
||||
// 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:
|
||||
// Update all the subtype sets of supertypes up the chain
|
||||
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)
|
||||
@ -325,6 +334,7 @@ export default class PocomathInstance {
|
||||
// add conversions from "from" to this one and all its supertypes:
|
||||
let nextSuper = type
|
||||
while (nextSuper) {
|
||||
if (this._priorTypes[nextSuper].has(from)) break
|
||||
this._typed.addConversion(
|
||||
{from, to: nextSuper, convert: spec.from[from]})
|
||||
this._invalidateDependents(':' + nextSuper)
|
||||
@ -384,6 +394,7 @@ export default class PocomathInstance {
|
||||
if (!typeA) return typeB
|
||||
if (!typeB) return typeA
|
||||
if (typeA === 'any' || typeB === 'any') return 'any'
|
||||
if (typeA === 'ground' || typeB === 'ground') return 'ground'
|
||||
if (typeA === typeB) return typeA
|
||||
const subber = convert ? this._priorTypes : this._subtypes
|
||||
if (subber[typeB].has(typeA)) return typeB
|
||||
@ -409,7 +420,7 @@ export default class PocomathInstance {
|
||||
* signatures of operations, but which have not actually been installed:
|
||||
*/
|
||||
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 */
|
||||
@ -418,15 +429,15 @@ export default class PocomathInstance {
|
||||
/* For now, just allow a single template per base type; that
|
||||
* might need to change later:
|
||||
*/
|
||||
if (base in this._templateTypes) {
|
||||
if (spec !== this._templateTypes[base].spec) {
|
||||
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._templateTypes[base] = {type, spec}
|
||||
this.Templates[base] = {type, spec}
|
||||
}
|
||||
|
||||
/* Used internally by install, see the documentation there */
|
||||
@ -469,7 +480,7 @@ export default class PocomathInstance {
|
||||
for (const word of type.split(/[<>]/)) {
|
||||
if (word.length == 0) continue
|
||||
if (this._templateParam(word)) continue
|
||||
this._usedTypes.add(word)
|
||||
this._seenTypes.add(word)
|
||||
this._addAffect(':' + word, name)
|
||||
}
|
||||
}
|
||||
@ -497,20 +508,20 @@ export default class PocomathInstance {
|
||||
* and if it has no implementations so far, set them up.
|
||||
*/
|
||||
_invalidate(name) {
|
||||
if (this._doomed.has(name)) {
|
||||
/* In the midst of a circular invalidation, so do nothing */
|
||||
return
|
||||
}
|
||||
if (this._invalid.has(name)) return
|
||||
if (!(name in this._imps)) {
|
||||
this._imps[name] = {}
|
||||
}
|
||||
this._doomed.add(name)
|
||||
this._invalid.add(name)
|
||||
this._invalidateDependents(name)
|
||||
this._doomed.delete(name)
|
||||
const self = this
|
||||
Object.defineProperty(this, name, {
|
||||
configurable: true,
|
||||
get: () => self._bundle(name)
|
||||
get: () => {
|
||||
const result = self._bundle(name)
|
||||
self._invalid.delete(name)
|
||||
return result
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -541,7 +552,7 @@ export default class PocomathInstance {
|
||||
for (const type of typesOfSignature(entry[0])) {
|
||||
if (type in this.Types) continue
|
||||
const baseType = type.split('<')[0]
|
||||
if (baseType in this._templateTypes) continue
|
||||
if (baseType in this.Templates) continue
|
||||
keep = false
|
||||
break
|
||||
}
|
||||
@ -578,16 +589,10 @@ export default class PocomathInstance {
|
||||
behavior.instantiations = 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) {
|
||||
instantiationSet.add(instType)
|
||||
for (const other of this._priorTypes[instType]) {
|
||||
instantiationSet.add(other)
|
||||
}
|
||||
for (const instType of behavior.instantiations) {
|
||||
instantiationSet.add(instType)
|
||||
for (const other of this._priorTypes[instType]) {
|
||||
instantiationSet.add(other)
|
||||
}
|
||||
}
|
||||
|
||||
@ -595,7 +600,7 @@ export default class PocomathInstance {
|
||||
if (!(instType in this.Types)) continue
|
||||
if (this.Types[instType] === anySpec) continue
|
||||
const signature =
|
||||
substituteInSig(trimSignature, theTemplateParam, instType)
|
||||
substituteInSig(rawSignature, theTemplateParam, instType)
|
||||
/* Don't override an explicit implementation: */
|
||||
if (signature in imps) continue
|
||||
const uses = new Set()
|
||||
@ -622,15 +627,15 @@ export default class PocomathInstance {
|
||||
/* Now add the catchall signature */
|
||||
let templateCall = `<${theTemplateParam}>`
|
||||
/* Relying here that the base of 'Foo<T>' is 'Foo': */
|
||||
let baseSignature = trimSignature.replaceAll(templateCall, '')
|
||||
let baseSignature = rawSignature.replaceAll(templateCall, '')
|
||||
/* Any remaining template params are top-level */
|
||||
const signature = substituteInSig(
|
||||
baseSignature, theTemplateParam, 'any')
|
||||
baseSignature, theTemplateParam, 'ground')
|
||||
/* The catchall signature has to detect the actual type of the call
|
||||
* and add the new instantiations.
|
||||
* First, prepare the type inference data:
|
||||
*/
|
||||
const parTypes = trimSignature.split(',')
|
||||
const parTypes = rawSignature.split(',')
|
||||
const restParam = (parTypes[parTypes.length-1].slice(0,3) === '...')
|
||||
const topTyper = entity => this.typeOf(entity)
|
||||
const inferences = parTypes.map(
|
||||
@ -639,7 +644,7 @@ export default class PocomathInstance {
|
||||
theTemplateParam,
|
||||
topTyper,
|
||||
this.joinTypes.bind(this),
|
||||
this._templateTypes))
|
||||
this.Templates))
|
||||
if (inferences.every(x => !x)) { // all false
|
||||
throw new SyntaxError(
|
||||
`Cannot find template parameter in ${rawSignature}`)
|
||||
@ -700,7 +705,6 @@ export default class PocomathInstance {
|
||||
usedConversions = true
|
||||
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
||||
if (instantiateFor === 'any') {
|
||||
// Need a more informative error message here
|
||||
throw TypeError(
|
||||
`In call to ${name}, no type unifies arguments `
|
||||
+ args.toString() + '; of types ' + argTypes.toString()
|
||||
@ -717,7 +721,7 @@ export default class PocomathInstance {
|
||||
* by instantiateFor, and for all of instantiateFor's "prior types"
|
||||
*/
|
||||
for (j = 0; j < parTypes.length; ++j) {
|
||||
if (wantTypes[j] !== parTypes[j] && wantTypes[j].includes('<')) {
|
||||
if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) {
|
||||
// actually used the param and is a template
|
||||
self._ensureTemplateTypes(parTypes[j], instantiateFor)
|
||||
}
|
||||
@ -762,8 +766,9 @@ export default class PocomathInstance {
|
||||
if (self._typed.isTypedFunction(refs[simplifiedDep])) {
|
||||
const subsig = substituteInSig(
|
||||
needsig, theTemplateParam, instantiateFor)
|
||||
innerRefs[dep] = self._typed.find(
|
||||
refs[simplifiedDep], subsig)
|
||||
let resname = simplifiedDep
|
||||
if (resname === 'self') resname = name
|
||||
innerRefs[dep] = self._pocoresolve(resname, subsig)
|
||||
} else {
|
||||
innerRefs[dep] = refs[simplifiedDep]
|
||||
}
|
||||
@ -841,7 +846,7 @@ export default class PocomathInstance {
|
||||
// can bundle up func, and grab its signature if need be
|
||||
let destination = this[func]
|
||||
if (needsig) {
|
||||
destination = this._typed.find(destination, needsig)
|
||||
destination = this._pocoresolve(func, needsig)
|
||||
}
|
||||
refs[dep] = destination
|
||||
}
|
||||
@ -886,27 +891,10 @@ export default class PocomathInstance {
|
||||
corrected_self_references.push(neededSig)
|
||||
continue
|
||||
}
|
||||
// No exact match, have to 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
|
||||
// is too complicated to reproduce
|
||||
let foundSig = false
|
||||
const typeList = typesOfSignature(neededSig)
|
||||
for (const otherSig in imps) {
|
||||
const otherTypeList = typesOfSignature(otherSig)
|
||||
if (typeList.length !== otherTypeList.length) continue
|
||||
const allMatch = true
|
||||
for (let k = 0; k < typeList.length; ++k) {
|
||||
if (this._subtypes[otherTypeList[k]].has(typeList[k])) {
|
||||
continue
|
||||
}
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
if (allMatch) {
|
||||
foundSig = otherSig
|
||||
break
|
||||
}
|
||||
}
|
||||
const foundSig = this._findSubtypeImpl(imps, neededSig)
|
||||
if (foundSig) {
|
||||
corrected_self_references.push(foundSig)
|
||||
} else {
|
||||
@ -963,18 +951,14 @@ export default class PocomathInstance {
|
||||
if (wantsType in this.Types) return false
|
||||
// OK, need to generate the type from the template
|
||||
// Set up refines, before, test, and from
|
||||
const newTypeSpec = {}
|
||||
const template = this._templateTypes[base].spec
|
||||
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]
|
||||
if (instantiatorSpec.refines) {
|
||||
// Assuming all templates are covariant, for now
|
||||
this._maybeAddTemplateType(base, instantiatorSpec.refines)
|
||||
newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>`
|
||||
}
|
||||
let beforeTypes = []
|
||||
if (instantiatorSpec.before) {
|
||||
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
||||
@ -988,9 +972,8 @@ export default class PocomathInstance {
|
||||
if (beforeTypes.length > 0) {
|
||||
newTypeSpec.before = beforeTypes
|
||||
}
|
||||
newTypeSpec.test = template.test(instantiatorSpec.test)
|
||||
newTypeSpec.test = template.test(this._typeTests[instantiator])
|
||||
if (template.from) {
|
||||
newTypeSpec.from = {}
|
||||
for (let source in template.from) {
|
||||
const instSource = substituteInSig(
|
||||
source, theTemplateParam, instantiator)
|
||||
@ -1005,16 +988,87 @@ export default class PocomathInstance {
|
||||
for (const iFrom in instantiatorSpec.from) {
|
||||
const finalSource = substituteInSig(
|
||||
instSource, templateFromParam, iFrom)
|
||||
newTypeSpec.from[finalSource] = template.from[source](
|
||||
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 {
|
||||
newTypeSpec.from[instSource] = template.from[source]
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ export function subsetOfKeys(set, obj) {
|
||||
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 */
|
||||
export function typesOfSignature(signature) {
|
||||
return new Set(signature.split(/[^\w\d]/).filter(s => s.length))
|
||||
|
@ -4,6 +4,7 @@ export * from './Types/generic.mjs'
|
||||
|
||||
export const add = reducingOperation
|
||||
export const gcd = reducingOperation
|
||||
export {identity} from './identity.mjs'
|
||||
export {lcm} from './lcm.mjs'
|
||||
export {mean} from './mean.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
|
||||
}
|
@ -7,14 +7,27 @@ export const isZero = {
|
||||
}
|
||||
|
||||
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,
|
||||
'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 = {
|
||||
'T,T': ({'equal(T.T)': eq}) => (x,y) => !(eq(x,y))
|
||||
'any,any': ({equal}) => (x,y) => !(equal(x,y))
|
||||
}
|
||||
|
||||
export const larger = {
|
||||
|
@ -1,10 +1,12 @@
|
||||
import gcdType from '../generic/gcdType.mjs'
|
||||
import {identity} from '../generic/identity.mjs'
|
||||
|
||||
export * from './Types/number.mjs'
|
||||
|
||||
export {abs} from './abs.mjs'
|
||||
export {add} from './add.mjs'
|
||||
export {compare} from './compare.mjs'
|
||||
export const conjugate = {number: () => identity}
|
||||
export const gcd = gcdType('NumInt')
|
||||
export {invert} from './invert.mjs'
|
||||
export {isZero} from './isZero.mjs'
|
||||
|
@ -12,7 +12,7 @@ export const floor = {
|
||||
// entry with type `bigint|NumInt|GaussianInteger` because they couldn't
|
||||
// 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)
|
||||
return Math.floor(n)
|
||||
},
|
||||
|
@ -4,18 +4,10 @@ import * as numbers from './number/native.mjs'
|
||||
import * as bigints from './bigint/native.mjs'
|
||||
import * as complex from './complex/native.mjs'
|
||||
import * as tuple from './tuple/native.mjs'
|
||||
// Most of tuple is not ready yet:
|
||||
const tupleReady = {
|
||||
Tuple: tuple.Tuple,
|
||||
equal: tuple.equal,
|
||||
isZero: tuple.isZero,
|
||||
length: tuple.length,
|
||||
tuple: tuple.tuple
|
||||
}
|
||||
import * as generic from './generic/all.mjs'
|
||||
import * as ops from './ops/all.mjs'
|
||||
|
||||
const math = PocomathInstance.merge(
|
||||
'math', numbers, bigints, complex, tupleReady, generic, ops)
|
||||
'math', numbers, bigints, complex, tuple, generic, ops)
|
||||
|
||||
export default math
|
||||
|
@ -23,7 +23,7 @@ Tuple.installType('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: [u]}), // singleton promotion
|
||||
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.
|
||||
@ -32,48 +32,48 @@ Tuple.installType('Tuple<T>', {
|
||||
})
|
||||
|
||||
Tuple.promoteUnary = {
|
||||
'Tuple<T>': ({'self(T)': me}) => t => ({elts: t.elts.map(me)})
|
||||
'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}) => (s,t) => {
|
||||
'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.append(meB(s.elts[i], t.elts[i]))
|
||||
else results.append(meU(s.elts[i]))
|
||||
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.append(meU(t.elts[i]))
|
||||
if (i < t.elts.length) result.push(meU(t.elts[i]))
|
||||
else break
|
||||
}
|
||||
return {elts: result}
|
||||
return tuple(...result)
|
||||
}
|
||||
}
|
||||
|
||||
Tuple.promoteBinary = {
|
||||
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB}) => (s,t) => {
|
||||
'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.append(meB(s.elts[i], t.elts[i]))
|
||||
result.push(meB(s.elts[i], t.elts[i]))
|
||||
}
|
||||
return {elts: result}
|
||||
return tuple(...result)
|
||||
}
|
||||
}
|
||||
|
||||
Tuple.promoteBinaryStrict = {
|
||||
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB}) => (s,t) => {
|
||||
'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.append(meB(s.elts[i], t.elts[i]))
|
||||
result.push(meB(s.elts[i], t.elts[i]))
|
||||
}
|
||||
return {elts: result}
|
||||
return tuple(...result)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
export * from './Types/Tuple.mjs'
|
||||
|
||||
export const equal = {
|
||||
// Change this to a template implementation (or maybe add template
|
||||
// implementation to handle matching types, and have mixed template-base
|
||||
// method returning false to catch test of two tuples of different types.
|
||||
'Tuple,Tuple': ({self, length}) => (s,t) => {
|
||||
if (length(s) !== length(t)) return false
|
||||
for (let i = 0; i < length(s); ++i) {
|
||||
if (!self(s.elts[i], t.elts[i])) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ import {Tuple} from './Types/Tuple.mjs'
|
||||
export const add = Tuple.promoteBinaryUnary
|
||||
export const complex = Tuple.promoteBinaryStrict
|
||||
export const conjugate = Tuple.promoteUnary
|
||||
// May want to replace equal with compare based on lexicographic ordering?
|
||||
export {equal} from './equal.mjs'
|
||||
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'
|
||||
@ -14,6 +14,7 @@ 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
|
||||
|
||||
|
@ -18,9 +18,10 @@ describe('A custom instance', () => {
|
||||
it("works when partially assembled", () => {
|
||||
bw.install(complex)
|
||||
// Not much we can call without any number types:
|
||||
const i3 = {re: 0, im: 3}
|
||||
assert.deepStrictEqual(bw.complex(0, 3), i3)
|
||||
assert.deepStrictEqual(bw.chain(0).complex(3).value, i3)
|
||||
assert.deepStrictEqual(bw.complex(undefined, undefined), undefined)
|
||||
assert.deepStrictEqual(
|
||||
bw.chain(undefined).complex(undefined).value,
|
||||
undefined)
|
||||
// Don't have a way to negate things, for example:
|
||||
assert.throws(() => bw.negate(2), TypeError)
|
||||
})
|
||||
@ -34,7 +35,7 @@ describe('A custom instance', () => {
|
||||
assert.deepStrictEqual(
|
||||
bw.subtract(16, bw.add(3, bw.complex(0,4), 2)),
|
||||
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", () => {
|
||||
@ -130,12 +131,9 @@ describe('A custom instance', () => {
|
||||
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
||||
'Merge to Complex')
|
||||
// The following is the current behavior, since 3 converts to 3+0i
|
||||
// and 3n converts to 3n+0ni, both of which are technically Complex.
|
||||
// This will remain the case even with templated Complex, because
|
||||
// both Complex<bigint> and Complex<NumInt> will refine Complex (for the
|
||||
// sake of catching new specializations). Not sure whether that will be
|
||||
// OK or a problem that will have to be dealt with.
|
||||
assert.strictEqual(inst.typeMerge(3, 3n), 'Merge to Complex')
|
||||
// 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...
|
||||
|
@ -18,11 +18,97 @@ describe('tuple', () => {
|
||||
{elts: [math.complex(3), math.complex(2n), math.complex(5.2)]})
|
||||
})
|
||||
|
||||
it('can be tested for zero', () => {
|
||||
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