diff --git a/README.md b/README.md index 75a378e..b97781d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,3 @@ Hopefully this shows promise. It is an evolution of the concept first prototyped Note that Pocomath allows one implementation to depend just on a specific signature of another function, for efficiency's sake (if for example 'bar(Matrix)' knows it will only call 'foo(Matrix)', it avoids another type-dispatch). That capability is used in sqrt, for example. Pocomath also lazily reloads operations that depend on the config when that changes, and if an operation has a signature mentioning an undefined type, that signature is ignored until the type is installed, at which point the function lazily redefines itself to use the additional signature. - -Pocomath now also allows template operations and template types, also built on top of typed-function (but candidates for integration therein). This is used to make many operations more specific, implement a type-homogeneous Tuple type, and make Complex numbers be type-homogeneous (which it seems like it always should be). One of the cutest consequences of this approach is that with careful definitions of the `Complex` templates, one gets a working quaternion data type absolutely for free as `Complex>` (and integral quaternions as `Complex>`, etc.) - -It also now has a facility to adapt a third-party numeric class as a type in Pocomath, see `src/generic/all.mjs` and `src/generic/Types/adapted.mjs`, which it uses by way of example to incorporate fraction.js Fraction objects into Pocomath. \ No newline at end of file diff --git a/package.json5 b/package.json5 index 1862178..a0647ae 100644 --- a/package.json5 +++ b/package.json5 @@ -24,7 +24,6 @@ }, dependencies: { 'bigint-isqrt': '^0.2.1', - 'fraction.js': '^4.2.0', 'typed-function': '^3.0.0', }, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eef07ec..4063068 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,13 +2,11 @@ lockfileVersion: 5.4 specifiers: bigint-isqrt: ^0.2.1 - fraction.js: ^4.2.0 mocha: ^10.0.0 typed-function: ^3.0.0 dependencies: bigint-isqrt: 0.2.1 - fraction.js: 4.2.0 typed-function: 3.0.0 devDependencies: @@ -194,10 +192,6 @@ packages: hasBin: true dev: true - /fraction.js/4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} - dev: false - /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true diff --git a/src/bigint/absquare.mjs b/src/bigint/absquare.mjs deleted file mode 100644 index 587e8ec..0000000 --- a/src/bigint/absquare.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import Returns from '../core/Returns.mjs' -export * from './Types/bigint.mjs' - -/* Absolute value squared */ -export const absquare = { - bigint: ({'square(bigint)': sqb}) => Returns('bigint', b => sqb(b)) -} diff --git a/src/bigint/add.mjs b/src/bigint/add.mjs index c4291ac..1cd296d 100644 --- a/src/bigint/add.mjs +++ b/src/bigint/add.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -export const add = {'bigint,bigint': () => Returns('bigint', (a,b) => a+b)} +export const add = {'bigint,bigint': () => (a,b) => a+b} diff --git a/src/bigint/compare.mjs b/src/bigint/compare.mjs index 097dfca..ab830ab 100644 --- a/src/bigint/compare.mjs +++ b/src/bigint/compare.mjs @@ -1,7 +1,5 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' export const compare = { - 'bigint,bigint': () => Returns( - 'boolean', (a,b) => a === b ? 0n : (a > b ? 1n : -1n)) + 'bigint,bigint': () => (a,b) => a === b ? 0n : (a > b ? 1n : -1n) } diff --git a/src/bigint/divide.mjs b/src/bigint/divide.mjs index 492893a..4554457 100644 --- a/src/bigint/divide.mjs +++ b/src/bigint/divide.mjs @@ -1,13 +1,12 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' export const divide = { 'bigint,bigint': ({config, 'quotient(bigint,bigint)': quot}) => { - if (config.predictable) return Returns('bigint', (n,d) => quot(n,d)) - return Returns('bigint|undefined', (n, d) => { + if (config.predictable) return quot + return (n, d) => { const q = n/d if (q * d == n) return q return undefined - }) + } } } diff --git a/src/bigint/isZero.mjs b/src/bigint/isZero.mjs index 02aca57..0efa71c 100644 --- a/src/bigint/isZero.mjs +++ b/src/bigint/isZero.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -export const isZero = {bigint: () => Returns('boolean', b => b === 0n)} +export const isZero = {bigint: () => b => b === 0n} diff --git a/src/bigint/multiply.mjs b/src/bigint/multiply.mjs index e80cee0..e19959e 100644 --- a/src/bigint/multiply.mjs +++ b/src/bigint/multiply.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -export const multiply = {'bigint,bigint': () => Returns('bigint', (a,b) => a*b)} +export const multiply = {'bigint,bigint': () => (a,b) => a*b} diff --git a/src/bigint/native.mjs b/src/bigint/native.mjs index b387615..6458912 100644 --- a/src/bigint/native.mjs +++ b/src/bigint/native.mjs @@ -1,12 +1,11 @@ import gcdType from '../generic/gcdType.mjs' -import {identityType} from '../generic/identity.mjs' +import {identity} from '../generic/identity.mjs' export * from './Types/bigint.mjs' -export {absquare} from './absquare.mjs' export {add} from './add.mjs' export {compare} from './compare.mjs' -export const conjugate = {bigint: identityType('bigint')} +export const conjugate = {bigint: () => identity} export {divide} from './divide.mjs' export const gcd = gcdType('bigint') export {isZero} from './isZero.mjs' diff --git a/src/bigint/negate.mjs b/src/bigint/negate.mjs index ecd51f1..d44cdb0 100644 --- a/src/bigint/negate.mjs +++ b/src/bigint/negate.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -export const negate = {bigint: () => Returns('bigint', b => -b)} +export const negate = {bigint: () => b => -b} diff --git a/src/bigint/one.mjs b/src/bigint/one.mjs index 8e8a7f2..f548a65 100644 --- a/src/bigint/one.mjs +++ b/src/bigint/one.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -export const one = {bigint: () => Returns('bigint', () => 1n)} +export const one = {bigint: () => () => 1n} diff --git a/src/bigint/quotient.mjs b/src/bigint/quotient.mjs index c1a086a..589adc3 100644 --- a/src/bigint/quotient.mjs +++ b/src/bigint/quotient.mjs @@ -1,14 +1,13 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -/* Returns the floor integer approximation to n/d */ +/* Returns the best integer approximation to n/d */ export const quotient = { - 'bigint,bigint': ({'sign(bigint)': sgn}) => Returns('bigint', (n, d) => { + 'bigint,bigint': ({'sign(bigint)': sgn}) => (n, d) => { const dSgn = sgn(d) if (dSgn === 0n) return 0n if (sgn(n) === dSgn) return n/d const quot = n/d if (quot * d == n) return quot return quot - 1n - }) + } } diff --git a/src/bigint/roundquotient.mjs b/src/bigint/roundquotient.mjs index 57fb941..c6763a2 100644 --- a/src/bigint/roundquotient.mjs +++ b/src/bigint/roundquotient.mjs @@ -1,9 +1,8 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' /* Returns the closest integer approximation to n/d */ export const roundquotient = { - 'bigint,bigint': ({'sign(bigint)': sgn}) => Returns('bigint', (n, d) => { + 'bigint,bigint': ({'sign(bigint)': sgn}) => (n, d) => { const dSgn = sgn(d) if (dSgn === 0n) return 0n const candidate = n/d @@ -12,5 +11,5 @@ export const roundquotient = { if (2n * rem > absd) return candidate + dSgn if (-2n * rem >= absd) return candidate - dSgn return candidate - }) + } } diff --git a/src/bigint/sign.mjs b/src/bigint/sign.mjs index c811df2..af48e05 100644 --- a/src/bigint/sign.mjs +++ b/src/bigint/sign.mjs @@ -1,10 +1,9 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' export const sign = { - bigint: () => Returns('bigint', b => { + bigint: () => b => { if (b === 0n) return 0n if (b > 0n) return 1n return -1n - }) + } } diff --git a/src/bigint/sqrt.mjs b/src/bigint/sqrt.mjs index 01ef0b0..ad6bd67 100644 --- a/src/bigint/sqrt.mjs +++ b/src/bigint/sqrt.mjs @@ -1,39 +1,30 @@ -import Returns from '../core/Returns.mjs' -import isqrt from 'bigint-isqrt' export * from './Types/bigint.mjs' +import isqrt from 'bigint-isqrt' export const sqrt = { - bigint: ({ - config, - 'complex(bigint,bigint)': cplx, - 'negate(bigint)': neg - }) => { + bigint: ({config, complex, 'self(Complex)': complexSqrt}) => { if (config.predictable) { // Don't just return the constant isqrt here because the object // gets decorated with info that might need to be different // for different PocomathInstancss - return Returns('bigint', b => isqrt(b)) + return b => isqrt(b) } - if (!cplx) { - return Returns('bigint|undefined', b => { + if (!complexSqrt) { + return b => { if (b >= 0n) { const trial = isqrt(b) if (trial * trial === b) return trial } return undefined - }) - } - return Returns('bigint|Complex|undefined', b => { - if (b === undefined) return undefined - let real = true - if (b < 0n) { - b = neg(b) - real = false } - const trial = isqrt(b) - if (trial * trial !== b) return undefined - if (real) return trial - return cplx(0n, trial) - }) + } + return b => { + if (b >= 0n) { + const trial = isqrt(b) + if (trial * trial === b) return trial + return undefined + } + return complexSqrt(complex(b)) + } } } diff --git a/src/bigint/zero.mjs b/src/bigint/zero.mjs index e9fe83b..0c63a1a 100644 --- a/src/bigint/zero.mjs +++ b/src/bigint/zero.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/bigint.mjs' -export const zero = {bigint: () => Returns('bigint', () => 0n)} +export const zero = {bigint: () => () => 0n} diff --git a/src/complex/Types/Complex.mjs b/src/complex/Types/Complex.mjs index 4d63417..e7644b9 100644 --- a/src/complex/Types/Complex.mjs +++ b/src/complex/Types/Complex.mjs @@ -1,31 +1,30 @@ -import {Returns, returnTypeOf} from '../../core/Returns.mjs' import PocomathInstance from '../../core/PocomathInstance.mjs' +/* Use a plain object with keys re and im for a complex; note the components + * can be any type (for this proof-of-concept; in reality we'd want to + * insist on some numeric or scalar supertype). + */ +function isComplex(z) { + return z && typeof z === 'object' && 're' in z && 'im' in z +} + const Complex = new PocomathInstance('Complex') -// Now the template type: Complex numbers are actually always homogeneous -// in their component types. For an explanation of the meanings of the -// properties, see ../../tuple/Types/Tuple.mjs -Complex.installType('Complex', { - base: z => z && typeof z === 'object' && 're' in z && 'im' in z, - test: testT => z => testT(z.re) && testT(z.im), - infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]), +Complex.installType('Complex', { + test: isComplex, from: { - T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T) - U: convert => u => { - const t = convert(u) - return ({re: t, im: t-t}) - }, - 'Complex': convert => cu => ({re: convert(cu.re), im: convert(cu.im)}) + number: x => ({re: x, im: 0}) + } +}) +Complex.installType('GaussianInteger', { + test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint', + refines: 'Complex', + from: { + bigint: x => ({re: x, im: 0n}) } }) Complex.promoteUnary = { - 'Complex': ({ - T, - 'self(T)': me, - complex - }) => Returns( - `Complex<${returnTypeOf(me)}>`, z => complex(me(z.re), me(z.im))) + Complex: ({self,complex}) => z => complex(self(z.re), self(z.im)) } export {Complex} diff --git a/src/complex/abs.mjs b/src/complex/abs.mjs index 3ea0274..48d5a7b 100644 --- a/src/complex/abs.mjs +++ b/src/complex/abs.mjs @@ -1,20 +1,5 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const abs = { - 'Complex': ({ - sqrt, // Unfortunately no notation yet for the needed signature - 'absquare(T)': baseabsq, - 'absquare(Complex)': absq - }) => { - const midType = returnTypeOf(baseabsq) - const sqrtImp = sqrt.fromInstance.resolve('sqrt', midType, sqrt) - let retType = returnTypeOf(sqrtImp) - if (retType.includes('|')) { - // This is a bit of a hack, as it relies on all implementations of - // sqrt returning the "typical" return type as the first option - retType = retType.split('|',1)[0] - } - return Returns(retType, z => sqrtImp(absq(z))) - } + Complex: ({sqrt, 'absquare(Complex)': absq}) => z => sqrt(absq(z)) } diff --git a/src/complex/absquare.mjs b/src/complex/absquare.mjs index ab0194c..cfa1f30 100644 --- a/src/complex/absquare.mjs +++ b/src/complex/absquare.mjs @@ -1,27 +1,5 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const absquare = { - 'Complex': ({ - add, // no easy way to write the needed signature; if T is number - // it is number,number; but if T is Complex, it is just - // bigint,bigint. So unfortunately we depend on all of add, and - // we extract the needed implementation below. - 'self(T)': absq - }) => { - const midType = returnTypeOf(absq) - const addImp = add.fromInstance.resolve( - 'add', `${midType},${midType}`, add) - return Returns( - returnTypeOf(addImp), z => addImp(absq(z.re), absq(z.im))) - } + Complex: ({add, square}) => z => add(square(z.re), square(z.im)) } - -/* We could imagine notations that Pocomath could support that would simplify - * the above, maybe something like - * 'Complex': ({ - * 'self(T): U': absq, - * 'add(U,U):V': plus, - * V - * }) => Returns(V, z => plus(absq(z.re), absq(z.im))) - */ diff --git a/src/complex/add.mjs b/src/complex/add.mjs index 6172326..0c178d6 100644 --- a/src/complex/add.mjs +++ b/src/complex/add.mjs @@ -1,10 +1,22 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const add = { - 'Complex,Complex': ({ - T, - 'self(T,T)': me, - 'complex(T,T)': cplx - }) => Returns(`Complex<${T}>`, (w,z) => cplx(me(w.re, z.re), me(w.im, z.im))) + /* Relying on conversions for both complex + number and complex + bigint + * leads to an infinite loop when adding a number and a bigint, since they + * both convert to Complex. + */ + 'Complex,number': ({ + 'self(number,number)': addNum, + 'complex(number,number)': cplx + }) => (z,x) => cplx(addNum(z.re, x), z.im), + + 'Complex,bigint': ({ + 'self(bigint,bigint)': addBigInt, + 'complex(bigint,bigint)': cplx + }) => (z,x) => cplx(addBigInt(z.re, x), z.im), + + 'Complex,Complex': ({ + self, + complex + }) => (w,z) => complex(self(w.re, z.re), self(w.im, z.im)) } diff --git a/src/complex/arg.mjs b/src/complex/arg.mjs deleted file mode 100644 index d654795..0000000 --- a/src/complex/arg.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -/* arg is the "argument" or angle theta of z in its form r cis theta */ -export const arg = { - 'Complex': () => Returns('number', z => Math.atan2(z.im, z.re)) -} diff --git a/src/complex/associate.mjs b/src/complex/associate.mjs index 9aae6e1..f3106b4 100644 --- a/src/complex/associate.mjs +++ b/src/complex/associate.mjs @@ -1,18 +1,17 @@ -import Returns from '../core/Returns.mjs' 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(T)': zr, - 'one(T)': uno, - 'complex(T,T)': cplx, - 'negate(Complex)': neg - }) => Returns('boolean', (w,z) => { + '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, cplx(zr(z.re), uno(z.im))) + const ti = times(z, complex(zero(z.re), one(z.im))) return eq(w,ti) || eq(w,neg(ti)) - }) + } } diff --git a/src/complex/cbrtc.mjs b/src/complex/cbrtc.mjs deleted file mode 100644 index 118da60..0000000 --- a/src/complex/cbrtc.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -const TAU3 = 2 * Math.PI / 3 - -/* Complex cube root that returns all three roots as a tuple of complex. */ -/* follows the implementation in mathjs */ -/* Really only works for T = number at the moment because of arg and cbrt */ -export const cbrtc = { - 'Complex': ({ - 'arg(T)': theta, - 'divide(T,T)': div, - 'abs(Complex)': absval, - 'complex(T)': cplx, - 'cbrt(T)': cbrtT, - 'multiply(Complex,Complex)': mult, - 'cis(T)': cisT, - 'tuple(...Complex)': tup - }) => Returns('Tuple>', z => { - const arg3 = div(theta(z), 3) - const r = cplx(cbrtT(absval(z))) - return tup( - mult(r, cisT(arg3)), - mult(r, cisT(arg3 + TAU3)), - mult(r, cisT(arg3 - TAU3)) - ) - }) -} diff --git a/src/complex/cis.mjs b/src/complex/cis.mjs deleted file mode 100644 index fd541e7..0000000 --- a/src/complex/cis.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -/* Returns cosine plus i sin theta */ -export const cis = { - 'number': ({'complex(number,number)': cplx}) => Returns( - 'Complex', t => cplx(Math.cos(t), Math.sin(t)) - ) -} diff --git a/src/complex/complex.mjs b/src/complex/complex.mjs index 49cfa60..c34434c 100644 --- a/src/complex/complex.mjs +++ b/src/complex/complex.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export * from '../generic/Types/generic.mjs' @@ -7,16 +6,11 @@ export const complex = { * have a numeric/scalar type, e.g. by implementing subtypes in * typed-function */ - 'undefined': () => Returns('undefined', u => u), - 'undefined,any': () => Returns('undefined', (u, y) => u), - 'any,undefined': () => Returns('undefined', (x, u) => u), - 'undefined,undefined': () => Returns('undefined', (u, v) => u), - 'T,T': ({T}) => Returns(`Complex<${T}>`, (x, y) => ({re: x, im: y})), + 'undefined': () => u => u, + 'undefined,any': () => (u, y) => u, + 'any,undefined': () => (x, u) => u, + 'undefined,undefined': () => (u, v) => u, + 'T,T': () => (x, y) => ({re: x, im: y}), /* Take advantage of conversions in typed-function */ - // 'Complex': () => z => z - /* But help out because without templates built in to typed-function, - * type inference turns out to be too hard - */ - 'T': ({T, 'zero(T)': zr}) => Returns( - `Complex<${T}>`, x => ({re: x, im: zr(x)})) + Complex: () => z => z } diff --git a/src/complex/conjugate.mjs b/src/complex/conjugate.mjs index c81180e..3139506 100644 --- a/src/complex/conjugate.mjs +++ b/src/complex/conjugate.mjs @@ -1,11 +1,6 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const conjugate = { - 'Complex': ({ - T, - 'negate(T)': neg, - 'complex(T,T)': cplx - }) => Returns(`Complex<${T}>`, z => cplx(z.re, neg(z.im))) + Complex: ({negate, complex}) => z => complex(z.re, negate(z.im)) } diff --git a/src/complex/equalTT.mjs b/src/complex/equalTT.mjs index 6a84c9a..43fe0d1 100644 --- a/src/complex/equalTT.mjs +++ b/src/complex/equalTT.mjs @@ -1,28 +1,19 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const equalTT = { - 'Complex,Complex': ({ - T, - 'self(T,T)': me - }) => Returns('boolean', (w, z) => me(w.re, z.re) && me(w.im, z.im)), - // NOTE: Although I do not understand exactly why, with typed-function@3.0's - // matching algorithm, the above template must come first to ensure the - // most specific match to a template call. I.e, if one of the below - // comes first, a call with two complex numbers can match via conversions - // with (Complex>, Complex) (!, hopefully in some - // future iteration typed-function will be smart enough to prefer - // Complex, Complex. Possibly the problem is in Pocomath's bolted-on - // type resolution and the difficulty will go away when features are moved - // into typed-function. - 'Complex,T': ({ - 'isZero(T)': isZ, - 'self(T,T)': eqReal - }) => Returns('boolean', (z, x) => eqReal(z.re, x) && isZ(z.im)), + 'Complex,number': ({ + 'isZero(number)': isZ, + 'self(number,number)': eqNum + }) => (z, x) => eqNum(z.re, x) && isZ(z.im), - 'T,Complex': ({ - 'isZero(T)': isZ, - 'self(T,T)': eqReal - }) => Returns('boolean', (b, z) => eqReal(z.re, b) && isZ(z.im)), + 'Complex,bigint': ({ + 'isZero(bigint)': isZ, + 'self(bigint,bigint)': eqBigInt + }) => (z, b) => eqBigInt(z.re, b) && isZ(z.im), + 'Complex,Complex': ({self}) => (w,z) => self(w.re, z.re) && self(w.im, z.im), + + 'GaussianInteger,GaussianInteger': ({ + 'self(bigint,bigint)': eq + }) => (a,b) => eq(a.re, b.re) && eq(a.im, b.im) } diff --git a/src/complex/extendToComplex.mjs b/src/complex/extendToComplex.mjs index 62e2e88..21e1a29 100644 --- a/src/complex/extendToComplex.mjs +++ b/src/complex/extendToComplex.mjs @@ -15,17 +15,4 @@ export default async function extendToComplex(pmath) { // Guess it wasn't a method available in complex; no worries } } - // Since extension to complex was specifically requested, instantiate - // all of the templates so that the associated type conversions will - // be available to make function calls work immediately: - for (const baseType in pmath.Types) { - if (baseType in pmath.Templates || baseType.includes('<')) { - continue // don't mess with templates - } - const ignore = new Set(['undefined', 'any', 'T', 'ground']) - if (ignore.has(baseType)) continue - // (What we really want is a check for "numeric" types but we don't - // have that concept (yet?)). If we did, we'd instantiate just for those... - pmath.instantiateTemplate('Complex', baseType) - } } diff --git a/src/complex/gcd.mjs b/src/complex/gcd.mjs index be90e4a..238d811 100644 --- a/src/complex/gcd.mjs +++ b/src/complex/gcd.mjs @@ -1,25 +1,19 @@ import PocomathInstance from '../core/PocomathInstance.mjs' -import Returns from '../core/Returns.mjs' -import * as Complex from './Types/Complex.mjs' +import * as Complex from './Types/Complex.mjs' import gcdType from '../generic/gcdType.mjs' -const gcdComplexRaw = {} -Object.assign(gcdComplexRaw, gcdType('Complex')) -Object.assign(gcdComplexRaw, gcdType('Complex')) const imps = { - gcdComplexRaw, + gcdGIRaw: gcdType('GaussianInteger'), gcd: { // Only return gcds with positive real part - 'Complex,Complex': ({ - T, - 'gcdComplexRaw(Complex,Complex)': gcdRaw, - 'sign(T)': sgn, - 'one(T)': uno, - 'negate(Complex)': neg - }) => Returns(`Complex<${T}>`, (z,m) => { + 'GaussianInteger,GaussianInteger': ({ + 'gcdGIRaw(GaussianInteger,GaussianInteger)': gcdRaw, + 'sign(bigint)': sgn, + 'negate(GaussianInteger)': neg + }) => (z,m) => { const raw = gcdRaw(z, m) - if (sgn(raw.re) === uno(raw.re)) return raw + if (sgn(raw.re) === 1n) return raw return neg(raw) - }) + } } } diff --git a/src/complex/invert.mjs b/src/complex/invert.mjs index ce1b932..dc7779a 100644 --- a/src/complex/invert.mjs +++ b/src/complex/invert.mjs @@ -1,16 +1,9 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const invert = { - 'Complex': ({ - T, - 'conjugate(Complex)': conj, - 'absquare(Complex)': asq, - 'complex(T,T)': cplx, - 'divide(T,T)': div - }) => Returns(`Complex<${T}>`, z => { - const c = conj(z) - const d = asq(z) - return cplx(div(c.re, d), div(c.im, d)) - }) + Complex: ({conjugate, absquare, complex, divide}) => z => { + const c = conjugate(z) + const d = absquare(z) + return complex(divide(c.re, d), divide(c.im, d)) + } } diff --git a/src/complex/isReal.mjs b/src/complex/isReal.mjs deleted file mode 100644 index 57eb7ee..0000000 --- a/src/complex/isReal.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import Returns from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -export const isReal = { - 'Complex': ({'equal(T,T)': eq, 'add(T,T)': plus}) => Returns( - 'boolean', z => eq(z.re, plus(z.re, z.im))) -} diff --git a/src/complex/isZero.mjs b/src/complex/isZero.mjs index 3e10c9b..1eaad7f 100644 --- a/src/complex/isZero.mjs +++ b/src/complex/isZero.mjs @@ -1,7 +1,5 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const isZero = { - 'Complex': ({'self(T)': me}) => Returns( - 'boolean', z => me(z.re) && me(z.im)) + Complex: ({self}) => z => self(z.re) && self(z.im) } diff --git a/src/complex/multiply.mjs b/src/complex/multiply.mjs index 5d9edc6..e1b46fe 100644 --- a/src/complex/multiply.mjs +++ b/src/complex/multiply.mjs @@ -1,20 +1,14 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const multiply = { - 'Complex,Complex': ({ - T, - 'complex(T,T)': cplx, - 'add(T,T)': plus, - 'subtract(T,T)': subt, - 'self(T,T)': me, - 'conjugate(T)': conj // makes quaternion multiplication work - }) => Returns( - `Complex<${T}>`, - (w,z) => { - const realpart = subt(me( w.re, z.re), me(conj(w.im), z.im)) - const imagpart = plus(me(conj(w.re), z.im), me( w.im, z.re)) - return cplx(realpart, imagpart) - } - ) + 'Complex,Complex': ({ + 'complex(any,any)': cplx, + add, + subtract, + self + }) => (w,z) => { + return cplx( + subtract(self(w.re, z.re), self(w.im, z.im)), + add(self(w.re, z.im), self(w.im, z.re))) + } } diff --git a/src/complex/native.mjs b/src/complex/native.mjs index eba3859..420ce88 100644 --- a/src/complex/native.mjs +++ b/src/complex/native.mjs @@ -3,24 +3,17 @@ export * from './Types/Complex.mjs' export {abs} from './abs.mjs' export {absquare} from './absquare.mjs' export {add} from './add.mjs' -export {arg} from './arg.mjs' export {associate} from './associate.mjs' -export {cbrtc} from './cbrtc.mjs' -export {cis} from './cis.mjs' export {complex} from './complex.mjs' export {conjugate} from './conjugate.mjs' export {equalTT} from './equalTT.mjs' export {gcd} from './gcd.mjs' export {invert} from './invert.mjs' -export {isReal} from './isReal.mjs' export {isZero} from './isZero.mjs' export {multiply} from './multiply.mjs' export {negate} from './negate.mjs' -export {polynomialRoot} from './polynomialRoot.mjs' -export {quaternion} from './quaternion.mjs' export {quotient} from './quotient.mjs' export {roundquotient} from './roundquotient.mjs' export {sqrt} from './sqrt.mjs' -export {sqrtc} from './sqrtc.mjs' export {zero} from './zero.mjs' diff --git a/src/complex/polynomialRoot.mjs b/src/complex/polynomialRoot.mjs deleted file mode 100644 index 7a6b9a3..0000000 --- a/src/complex/polynomialRoot.mjs +++ /dev/null @@ -1,118 +0,0 @@ -import Returns from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -export const polynomialRoot = { - 'Complex,...Complex': ({ - T, - 'tuple(...Complex)': tupCplx, - 'tuple(...T)': tupReal, - 'isZero(Complex)': zero, - 'complex(T)': C, - 'multiply(Complex,Complex)': mul, - 'divide(Complex,Complex)': div, - 'negate(Complex)': neg, - 'isReal(Complex)': real, - 'equalTT(Complex,Complex)': eq, - 'add(Complex,Complex)': plus, - 'subtract(Complex,Complex)': sub, - 'sqrtc(Complex)': sqt, - 'cbrtc(Complex)': cbt - }) => Returns(`Tuple<${T}>|Tuple>`, (constant, rest) => { - // helper to convert results to appropriate tuple type - const typedTup = arr => { - if (arr.every(real)) { - return tupReal.apply(tupReal, arr.map(z => z.re)) - } - return tupCplx.apply(tupCplx, arr) - } - - const coeffs = [constant, ...rest] - while (coeffs.length > 0 && zero(coeffs[coeffs.length - 1])) { - coeffs.pop() - } - if (coeffs.length < 2) { - } - switch (coeffs.length) { - case 0: case 1: - throw new RangeError( - `Polynomial [${constant}, ${rest}] must have at least one` - + 'non-zero non-constant coefficient') - case 2: // linear - return typedTup([neg(div(coeffs[0], coeffs[1]))]) - case 3: { // quadratic - const [c, b, a] = coeffs - const denom = mul(C(2), a) - const d1 = mul(b, b) - const d2 = mul(C(4), mul(a, c)) - if (eq(d1, d2)) { - return typedTup([div(neg(b), denom)]) - } - let discriminant = sqt(sub(d1, d2)) - return typedTup([ - div(sub(discriminant, b), denom), - div(sub(neg(discriminant), b), denom) - ]) - } - case 4: { // cubic, cf. https://en.wikipedia.org/wiki/Cubic_equation - const [d, c, b, a] = coeffs - const denom = neg(mul(C(3), a)) - const asqrd = mul(a, a) - const D0_1 = mul(b, b) - const bcubed = mul(D0_1, b) - const D0_2 = mul(C(3), mul(a, c)) - const D1_1 = plus( - mul(C(2), bcubed), mul(C(27), mul(asqrd, d))) - const abc = mul(a, mul(b, c)) - const D1_2 = mul(C(9), abc) - // Check for a triple root - if (eq(D0_1, D0_2) && eq(D1_1, D1_2)) { - return typedTup([div(b, denom)]) - } - const Delta0 = sub(D0_1, D0_2) - const Delta1 = sub(D1_1, D1_2) - const csqrd = mul(c, c) - const discriminant1 = plus( - mul(C(18), mul(abc, d)), mul(D0_1, csqrd)) - const discriminant2 = plus( - mul(C(4), mul(bcubed, d)), - plus( - mul(C(4), mul(a, mul(csqrd, c))), - mul(C(27), mul(asqrd, mul(d, d))))) - // See if we have a double root - if (eq(discriminant1, discriminant2)) { - return typedTup([ - div( - sub( - mul(C(4), abc), - plus(mul(C(9), mul(asqrd, d)), bcubed)), - mul(a, Delta0)), // simple root - div( - sub(mul(C(9), mul(a, d)), mul(b, c)), - mul(C(2), Delta0)) // double root - ]) - } - // OK, we have three distinct roots - let Ccubed - if (eq(D0_1, D0_2)) { - Ccubed = Delta1 - } else { - Ccubed = div( - plus( - Delta1, - sqt(sub( - mul(Delta1, Delta1), - mul(C(4), mul(Delta0, mul(Delta0, Delta0))))) - ), - C(2)) - } - const croots = cbt(Ccubed) - return typedTup(cbt(Ccubed).elts.map( - C => div(plus(b, plus(C, div(Delta0, C))), denom))) - } - default: - throw new RangeError( - 'only implemented for cubic or lower-order polynomials, ' - + `not ${JSON.stringify(coeffs)}`) - } - }) -} diff --git a/src/complex/quaternion.mjs b/src/complex/quaternion.mjs deleted file mode 100644 index a435395..0000000 --- a/src/complex/quaternion.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import Returns from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -// Might be nice to have type aliases! -export const quaternion = { - 'T,T,T,T': ({ - T, - 'complex(T,T)': cplxT, - 'complex(Complex,Complex)': quat - }) => Returns( - `Complex>`, - (r,i,j,k) => quat(cplxT(r,j), cplxT(i,k)) - ) -} diff --git a/src/complex/quotient.mjs b/src/complex/quotient.mjs index 6b53de1..20090e0 100644 --- a/src/complex/quotient.mjs +++ b/src/complex/quotient.mjs @@ -1,9 +1,5 @@ -import Returns from '../core/Returns.mjs' export * from './roundquotient.mjs' export const quotient = { - 'Complex,Complex': ({ - T, - 'roundquotient(Complex,Complex)': rq - }) => Returns(`Complex<${T}>`, (w,z) => rq(w,z)) + 'Complex,Complex': ({roundquotient}) => (w,z) => roundquotient(w,z) } diff --git a/src/complex/roundquotient.mjs b/src/complex/roundquotient.mjs index 474077f..c9b2cbc 100644 --- a/src/complex/roundquotient.mjs +++ b/src/complex/roundquotient.mjs @@ -1,19 +1,17 @@ -import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const roundquotient = { - 'Complex,Complex': ({ - T, - 'isZero(Complex)': isZ, - 'conjugate(Complex)': conj, - 'multiply(Complex,Complex)': mult, - 'absquare(Complex)': asq, - 'self(T,T)': me, - 'complex(T,T)': cplx - }) => Returns(`Complex<${T}>`, (n,d) => { + 'Complex,Complex': ({ + 'isZero(Complex)': isZ, + conjugate, + 'multiply(Complex,Complex)': mult, + absquare, + self, + complex + }) => (n,d) => { if (isZ(d)) return d - const cnum = mult(n, conj(d)) - const dreal = asq(d) - return cplx(me(cnum.re, dreal), me(cnum.im, dreal)) - }) + const cnum = mult(n, conjugate(d)) + const dreal = absquare(d) + return complex(self(cnum.re, dreal), self(cnum.im, dreal)) + } } diff --git a/src/complex/sqrt.mjs b/src/complex/sqrt.mjs index cee5278..3557c6d 100644 --- a/src/complex/sqrt.mjs +++ b/src/complex/sqrt.mjs @@ -1,34 +1,39 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const sqrt = { - 'Complex': ({ + Complex: ({ config, - 'sqrtc(Complex)': predictableSqrt, - 'isZero(T)': isZ, + isZero, + sign, + one, + add, + complex, + multiply, + self, + divide, + 'abs(Complex)': abs, + subtract }) => { - if (config.checkingDependency) return undefined - const complexReturns = returnTypeOf(predictableSqrt) - const baseReturns = complexReturns.slice(8, -1); // Complex if (config.predictable) { - return Returns(complexReturns, z => predictableSqrt(z)) - } - - return Returns( - `Complex<${baseReturns}>|${baseReturns}|undefined`, - z => { - let complexSqrt - try { - complexSqrt = predictableSqrt(z) - } catch (e) { - return undefined - } - if (complexSqrt.re === undefined || complexSqrt.im === undefined) { - return undefined - } - if (isZ(complexSqrt.im)) return complexSqrt.re - return complexSqrt + return z => { + const reOne = one(z.re) + if (isZero(z.im) && sign(z.re) === reOne) return complex(self(z.re)) + const reTwo = add(reOne, reOne) + return complex( + multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))), + self(divide(subtract(abs(z),z.re), reTwo)) + ) } - ) + } + return z => { + const reOne = one(z.re) + if (isZero(z.im) && sign(z.re) === reOne) return self(z.re) + const reTwo = add(reOne, reOne) + return complex( + multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))), + self(divide(subtract(abs(z),z.re), reTwo)) + ) + } } } + diff --git a/src/complex/sqrtc.mjs b/src/complex/sqrtc.mjs deleted file mode 100644 index e909ff7..0000000 --- a/src/complex/sqrtc.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' -export * from './Types/Complex.mjs' - -export const sqrtc = { - 'Complex': ({ - 'isZero(T)': isZ, - 'sign(T)': sgn, - 'one(T)': uno, - 'add(T,T)': plus, - 'complex(T)': cplxU, - 'complex(T,T)': cplxB, - 'multiply(T,T)': mult, - 'sqrt(T)': sqt, - 'divide(T,T)': div, - 'absquare(Complex)': absqC, - 'subtract(T,T)': sub - }) => { - if (isZ.checkingDependency) return undefined - let baseReturns = returnTypeOf(sqt) - if (baseReturns.includes('|')) { - // Bit of a hack, because it is relying on other implementations - // to list the "typical" value of sqrt first - baseReturns = baseReturns.split('|', 1)[0] - } - return Returns(`Complex<${baseReturns}>`, z => { - const reOne = uno(z.re) - if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(sqt(z.re)) - const myabs = sqt(absqC(z)) - const reTwo = plus(reOne, reOne) - const reQuot = div(plus(myabs, z.re), reTwo) - const imQuot = div(sub(myabs, z.re), reTwo) - if (reQuot === undefined || imQuot === undefined) { - throw new TypeError(`Cannot compute sqrt of ${z.re} + {z.im}i`) - } - return cplxB( - mult(sgn(z.im), sqt(div(plus(myabs, z.re), reTwo))), - sqt(div(sub(myabs, z.re), reTwo)) - ) - }) - } -} diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index cc1e089..05636d4 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -1,101 +1,22 @@ /* Core of pocomath: create an instance */ import typed from 'typed-function' -import {makeChain} from './Chain.mjs' import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs' -import {Returns, returnTypeOf} from './Returns.mjs' +import {makeChain} from './Chain.mjs' import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs' const anySpec = {} // fixed dummy specification of 'any' type -/* Like `.some(predicate)` but for collections */ -function exists(collection, predicate) { - for (const item of collection) if (predicate(item)) return true; - return false; -} - -/* Template/signature parsing stuff; should probably be moved to a - * separate file, but it's a bit interleaved at the moment - */ - const theTemplateParam = 'T' // First pass: only allow this one exact parameter -const restTemplateParam = `...${theTemplateParam}` -const templateCall = `<${theTemplateParam}>` const templateFromParam = 'U' // For defining covariant conversions -/* returns the pair [base, instance] for a template type. If the type - * is not a template, instance is undefined - */ -const templatePattern = /^\s*([^<\s]*)\s*<\s*(\S*)\s*>\s*$/ -function splitTemplate(type) { - if (!(type.includes('<'))) return [type, undefined] - const results = templatePattern.exec(type) - return [results[1], results[2]] -} -/* Returns the instance such that type is template instantiated for that - * instance. - */ -function whichInstance(type, template) { - if (template === theTemplateParam) return type - if (type === template) return '' - if (!(template.includes(templateCall))) { - throw new TypeError( - `Type ${template} is not a template, so can't produce ${type}`) - } - const [typeBase, typeInstance] = splitTemplate(type) - if (!typeInstance) { - throw new TypeError( - `Type ${type} not from a template, so isn't instance of ${template}`) - } - const [tempBase, tempInstance] = splitTemplate(template) - if (typeBase !== tempBase) { - throw new TypeError( - `Type ${type} has wrong top-level base to be instance of ${template}`) - } - return whichInstance(typeInstance, tempInstance) -} -/* Same as above, but for signatures */ -function whichSigInstance(sig, tempsig) { - const sigTypes = typeListOfSignature(sig) - const tempTypes = typeListOfSignature(tempsig) - const sigLength = sigTypes.length - if (sigLength === 0) { - throw new TypeError("No types in signature, so can't determine instance") - } - if (sigLength !== tempTypes.length) { - throw new TypeError(`Signatures ${sig} and ${tempsig} differ in length`) - } - let maybeInstance = whichInstance(sigTypes[0], tempTypes[0]) - for (let i = 1; i < sigLength; ++i) { - const currInstance = whichInstance(sigTypes[i], tempTypes[i]) - if (maybeInstance) { - if (currInstance && currInstance !== maybeInstance) { - throw new TypeError( - `Inconsistent instantiation of ${sig} from ${tempsig}`) - } - } else { - maybeInstance = currInstance - } - } - if (!maybeInstance) { - throw new TypeError( - `Signature ${sig} identical to ${tempsig}, not an instance`) - } - return maybeInstance -} - /* Returns a new signature just like sig but with the parameter replaced by * the type */ -const upperBounds = /\s*(\S*)\s*:\s*(\w*)\s*/g -function substituteInSignature(signature, parameter, type) { - const sig = signature.replaceAll(upperBounds, '$1') +function substituteInSig(sig, parameter, type) { const pattern = new RegExp("\\b" + parameter + "\\b", 'g') return sig.replaceAll(pattern, type) } -const UniversalType = 'ground' // name for a type that matches anything -let lastWhatToDo = null // used in an infinite descent check - export default class PocomathInstance { /* Disallowed names for ops; beware, this is slightly non-DRY * in that if a new top-level PocomathInstance method is added, its name @@ -104,20 +25,12 @@ export default class PocomathInstance { static reserved = new Set([ 'chain', 'config', - 'convert', 'importDependencies', 'install', 'installType', - 'instantiateTemplate', - 'isPriorTo', - 'isSubtypeOf', 'joinTypes', 'name', - 'returnTypeOf', - 'resolve', 'self', - 'subtypesOf', - 'supertypesOf', 'Templates', 'typeOf', 'Types', @@ -126,61 +39,22 @@ export default class PocomathInstance { constructor(name) { this.name = name - this._imps = {} // Pocomath implementations, with dependencies - this._TFimps = {} // typed-function implementations, dependencies resolved + this._imps = {} this._affects = {} this._typed = typed.create() this._typed.clear() - // The following is an additional typed-function universe for resolving - // uninstantiated template instances. It is linked to the main one in - // its onMismatch function, below: - this._metaTyped = typed.create() - this._metaTyped.clear() - this._metaTyped.addTypes([{name: UniversalType, test: () => true}]) - - // And these are the meta bindings: (I think we don't need separate - // invalidation for them as they are only accessed through a main call.) - this._meta = {} // The resulting typed-functions - this._metaTFimps = {} // and their implementations - const me = this - this._typed.onMismatch = (name, args, sigs) => { - if (me._invalid.has(name)) { - if (this._fixing === name) { - this._fixingCount += 1 - if (this._fixingCount > this._maxDepthSeen + 2) { - throw new ReferenceError( - `Infinite descent rebuilding ${name} on ${args}`) - } - } else { - this._fixingCount = 0 - } - // rebuild implementation and try again - const lastFixing = this._fixing - this._fixing = name - const value = me[name](...args) - this._fix = lastFixing - return value - } - const metaversion = me._meta[name] - if (metaversion) { - return me._meta[name](...args) - } - me._typed.throwMismatchError(name, args, sigs) - } - // List of types installed in the instance: (We start with just dummies - // for the 'any' type and for type parameters.) + 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 - // Types that have been moved into the metaverse: - this._metafiedTypes = new Set() - // All the template types that have been defined: + this.Types.ground = anySpec + // All the template types that have been defined this.Templates = {} - // And their instantiations: - this._instantiationsOf = {} - // The actual type testing functions: + // The actual type testing functions this._typeTests = {} - // For each type, gives all of its (in)direct subtypes in topo order: - this._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 * match in typed-function's dispatch algorithm before the given type. * This is important because if we instantiate a template, we must @@ -189,24 +63,27 @@ export default class PocomathInstance { */ this._priorTypes = {} this._seenTypes = new Set() // all types that have occurred in a signature - this._maxDepthSeen = 1 // deepest template nesting we've actually encountered this._invalid = new Set() // methods that are currently invalid this._config = {predictable: false, epsilon: 1e-12} + const self = this this.config = new Proxy(this._config, { get: (target, property) => target[property], set: (target, property, value) => { if (value !== target[property]) { target[property] = value - me._invalidateDependents('config') + self._invalidateDependents('config') } return true // successful } }) this._plainFunctions = new Set() // the names of the plain functions this._chainRepository = {} // place to store chainified functions - this.joinTypes = this.joinTypes.bind(me) - // Provide access to typed function conversion: - this.convert = this._typed.convert.bind(this._typed) + + this._installFunctions({ + typeOf: {ground: {uses: new Set(), does: () => () => 'any'}} + }) + + this.joinTypes = this.joinTypes.bind(this) } /** @@ -265,7 +142,7 @@ export default class PocomathInstance { * instantiation can be accomplished by prefixin the signature with an * exclamation point. */ - install = Returns('void', function(ops) { + install(ops) { if (ops instanceof PocomathInstance) { return _installInstance(ops) } @@ -291,17 +168,14 @@ export default class PocomathInstance { const stdimps = {} for (const [signature, does] of Object.entries(spec)) { const uses = new Set() - try { - does(dependencyExtractor(uses)) - } catch { - } + does(dependencyExtractor(uses)) stdimps[signature] = {uses, does} } stdFunctions[item] = stdimps } } this._installFunctions(stdFunctions) - }) + } /* Merge any number of PocomathInstances or modules: */ static merge(name, ...pieces) { @@ -312,28 +186,10 @@ export default class PocomathInstance { return result } - /* Determine the return type of an operation given an input signature */ - returnTypeOf = Returns('string', function(operation, signature) { - for (const type of typeListOfSignature(signature)) { - this._maybeInstantiate(type) - } - if (typeof operation !== 'string') { - operation = operation.name - } - const details = this._pocoFindSignature(operation, signature) - if (details) { - return returnTypeOf(details.fn, signature, this) - } - return returnTypeOf(this[operation], signature, this) - }) - /* Return a chain object for this instance with a given value: */ - chain = Returns( - sig => `Chain<${sig}>`, - function(value) { - return makeChain(value, this, this._chainRepository) - } - ) + chain(value) { + return makeChain(value, this, this._chainRepository) + } _installInstance(other) { for (const [type, spec] of Object.entries(other.Types)) { @@ -382,9 +238,9 @@ export default class PocomathInstance { for (const name of requiredSet) { for (const type of typeSet) { try { - const moduleName = `../${type}/${name}.mjs` - const module = await import(moduleName) - this.install(module) + const modName = `../${type}/${name}.mjs` + const mod = await import(modName) + this.install(mod) } catch (err) { if (!(err.message.includes('find'))) { // Not just a error because module doesn't exist @@ -405,7 +261,6 @@ export default class PocomathInstance { * @param {{test: any => bool, // the predicate for the type * from: Record => > // conversions * before: string[] // lower priority types - * refines: string // The type this is a subtype of * }} specification * * The second parameter of this function specifies the structure of the @@ -425,17 +280,16 @@ export default class PocomathInstance { * Implementation note: unlike _installFunctions below, we can make * the corresponding changes to the _typed object immediately */ - installType = Returns('void', function(type, spec) { - const parts = type.split(/[<,>]/).map(s => s.trim()) + installType(type, spec) { + const parts = type.split(/[<,>]/) if (this._templateParam(parts[0])) { throw new SyntaxError( `Type name '${type}' reserved for template parameter`) } if (parts.some(this._templateParam.bind(this))) { - // It's an uninstantiated template, deal with it separately + // It's a template, deal with it separately return this._installTemplateType(type, spec) } - const base = parts[0] if (type in this.Types) { if (spec !== this.Types[type]) { throw new SyntaxError(`Conflicting definitions of type ${type}`) @@ -448,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 @@ -464,14 +318,14 @@ export default class PocomathInstance { this._typeTests[type] = testFn this._typed.addTypes([{name: type, test: testFn}], beforeType) this.Types[type] = spec - this._subtypes[type] = [] + this._subtypes[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._addSubtypeTo(nextSuper, type) + this._subtypes[nextSuper].add(type) nextSuper = this.Types[nextSuper].refines } /* Now add conversions to this type */ @@ -481,7 +335,6 @@ export default class PocomathInstance { let nextSuper = type while (nextSuper) { if (this._priorTypes[nextSuper].has(from)) break - if (from === nextSuper) break this._typed.addConversion( {from, to: nextSuper, convert: spec.from[from]}) this._invalidateDependents(':' + nextSuper) @@ -490,21 +343,6 @@ export default class PocomathInstance { for (const subtype of this._subtypes[from]) { this._priorTypes[nextSuper].add(subtype) } - - /* Add the conversion in the metaverse if need be: */ - const [toBase, toInstance] = splitTemplate(nextSuper) - if (toInstance) { - const [fromBase, fromInstance] = splitTemplate(from) - if (!fromBase || fromBase !== toBase) { - this._metafy(from) - try { - this._metaTyped.addConversion( - {from, to: toBase, convert: spec.from[from]}) - } catch { - } - } - } - nextSuper = this.Types[nextSuper].refines } } @@ -514,131 +352,64 @@ export default class PocomathInstance { for (const fromtype in this.Types[to].from) { if (type == fromtype || (fromtype in this._subtypes - && this.isSubtypeOf(type, fromtype))) { - if (spec.refines == to || this.isSubtypeOf(spec.refines,to)) { + && this._subtypes[fromtype].has(type))) { + if (spec.refines == to || spec.refines in this._subtypes[to]) { throw new SyntaxError( `Conversion of ${type} to its supertype ${to} disallowed.`) } let nextSuper = to while (nextSuper) { - if (type === nextSuper) break - try { // may already be a conversion, and no way to ask - this._typed.addConversion({ - from: type, - to: nextSuper, - convert: this.Types[to].from[fromtype] - }) - this._invalidateDependents(':' + nextSuper) - /* Add the conversion in the metaverse if need be: */ - const [toBase, toInstance] = splitTemplate(nextSuper) - if (toInstance && base !== toBase) { - this._metafy(type) - this._metaTyped.addConversion({ - from: type, - to: toBase, - convert: this.Types[to].from[fromtype] - }) - } - } catch { - } + this._typed.addConversion({ + from: type, + to: nextSuper, + convert: this.Types[to].from[fromtype] + }) + this._invalidateDependents(':' + nextSuper) this._priorTypes[nextSuper].add(type) nextSuper = this.Types[nextSuper].refines } } } } - // Need to metafy ground types - if (type === base) { - this._metafy(type) - } // update the typeOf function const imp = {} - imp[type] = {uses: new Set(), does: () => Returns('string', () => type)} + imp[type] = {uses: new Set(), does: () => () => type} this._installFunctions({typeOf: imp}) - }) - - _metafy(type) { - if (this._metafiedTypes.has(type)) return - this._metaTyped.addTypes([{name: type, test: this._typeTests[type]}]) - this._metafiedTypes.add(type) } - _addSubtypeTo(sup, sub) { - if (this.isSubtypeOf(sub, sup)) return - const supSubs = this._subtypes[sup] - let i - for (i = 0; i < supSubs.length; ++i) { - if (this.isSubtypeOf(sub, supSubs[i])) break - } - supSubs.splice(i, 0, sub) - } - - /* Returns true if typeA is a strict subtype of type B */ - isSubtypeOf = Returns('boolean', function(typeA, typeB) { - // Currently not considering types to be a subtype of 'any' - if (typeB === 'any' || typeA === 'any') return false - return this._subtypes[typeB].includes(typeA) - }) - - /* Returns true if typeA is a subtype of or converts to type B */ - isPriorTo = Returns('boolean', function(typeA, typeB) { - if (!(typeB in this._priorTypes)) return false - return this._priorTypes[typeB].has(typeA) - }) - - /* Returns a list of the strict ubtypes of a given type, in topological - * sorted order (i.e, no type on the list contains one that comes after it). - */ - subtypesOf = Returns('Array', function(type) { - return this._subtypes[type] // should we clone? - }) - - /* Returns a list of the supertypes of a given type, starting with itself, - * in topological order - */ - supertypesOf = Returns('Array', function(type) { - const supList = [] - while (type) { - supList.push(type) - type = this.Types[type].refines - } - return supList - }) - - /* Returns the most refined type containing all the types in the array, - * with '' standing for the empty type for convenience. If the second + /* 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 = Returns('string', function(types, convert) { + 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 - const pick = convert ? 'has' : 'includes' - if (subber[typeB][pick](typeA)) return typeB + 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][pick](typeB)) return 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][pick](typeA)) return nextSuper + if (subber[nextSuper].has(typeA)) return nextSuper nextSuper = this.Types[nextSuper].refines } } @@ -648,14 +419,13 @@ export default class PocomathInstance { /* Returns a list of all types that have been mentioned in the * signatures of operations, but which have not actually been installed: */ - undefinedTypes = Returns('Array', function() { - return Array.from(this._seenTypes).filter( - t => !(t in this.Types || t in this.Templates)) - }) + undefinedTypes() { + return Array.from(this._seenTypes).filter(t => !(t in this.Types)) + } /* Used internally to install a template type */ _installTemplateType(type, spec) { - const [base] = splitTemplate(type) + const base = type.split('<')[0] /* For now, just allow a single template per base type; that * might need to change later: */ @@ -666,37 +436,7 @@ export default class PocomathInstance { } return } - - // install the "base type" in the meta universe: - let beforeType = UniversalType - for (const other of spec.before || []) { - if (other in this.templates) { - beforeType = other - break - } - } - this._metaTyped.addTypes([{name: base, test: spec.base}], beforeType) - // Add conversions to the base type: - if (spec.from && spec.from[theTemplateParam]) { - for (const ground of this._metafiedTypes) { - this._metaTyped.addConversion( - {from: ground, to: base, convert: spec.from[theTemplateParam]}) - } - } - this._instantiationsOf[base] = new Set() - - // update the typeOf function - const imp = {} - imp[type] = { - uses: new Set(['T']), - does: ({T}) => Returns('string', () => `${base}<${T}>`) - } - this._installFunctions({typeOf: imp}) - - // Invalidate any functions that reference this template type: - this._invalidateDependents(':' + base) - - // Nothing else actually happens until we match a template parameter + // Nothing actually happens until we match a template parameter this.Templates[base] = {type, spec} } @@ -727,27 +467,8 @@ export default class PocomathInstance { `Conflicting definitions of ${signature} for ${name}`) } } else { - /* Check if it's an ordinary non-template signature */ - let explicit = true - for (const type of typesOfSignature(signature)) { - for (const word of type.split(/[<>:\s]/)) { - if (this._templateParam(word)) { - explicit = false - break - } - } - if (!explicit) break - } - opImps[signature] = { - explicit, - resolved: false, - uses: behavior.uses, - does: behavior.does - } - if (!explicit) { - opImps[signature].hasInstantiations = {} - opImps[signature].needsInstantiations = new Set() - } + // Must avoid aliasing into another instance: + opImps[signature] = {uses: behavior.uses, does: behavior.does} for (const dep of behavior.uses) { const depname = dep.split('(', 1)[0] if (depname === 'self' || this._templateParam(depname)) { @@ -785,39 +506,14 @@ export default class PocomathInstance { /** * Reset an operation to require creation of typed-function, * and if it has no implementations so far, set them up. - * name is the name of the operation, badType is a type that has been - * invalidated, and reasons is a set of specific operations/signatures - * that have been invalidated */ - _invalidate(name, badType = '', reasons = new Set()) { + _invalidate(name) { + if (this._invalid.has(name)) return if (!(name in this._imps)) { this._imps[name] = {} - this._TFimps[name] = {} - this._metaTFimps[name] = {} } - // Go through each TF imp and invalidate it if need be - for (const [signature, imp] of Object.entries(this._TFimps[name])) { - if (imp.deferred - || (badType && signature.includes(badType)) - || exists(imp.uses, dep => { - const [func, sig] = dep.split(/[()]/) - return reasons.has(dep) - || (reasons.has(func) && !(sig in this._TFimps[func])) - })) { - // Invalidate this implementation: - delete this._TFimps[name][signature] - const behavior = imp.fromBehavior - if (behavior.explicit) { - behavior.resolved = false - } else { - delete behavior.hasInstantiations[imp.instance] - } - reasons.add(`${name}(${signature})`) - } - } - if (this._invalid.has(name)) return this._invalid.add(name) - this._invalidateDependents(name, badType, reasons) + this._invalidateDependents(name) const self = this Object.defineProperty(this, name, { configurable: true, @@ -831,14 +527,11 @@ export default class PocomathInstance { /** * Invalidate all the dependents of a given property of the instance - * reasons is a set of invalidated signatures */ - _invalidateDependents(name, badType, reasons = new Set()) { - if (name.charAt(0) === ':') badType = name.slice(1) - else reasons.add(name) + _invalidateDependents(name) { if (name in this._affects) { for (const ancestor of this._affects[name]) { - this._invalidate(ancestor, badType, reasons) + this._invalidate(ancestor) } } } @@ -852,15 +545,13 @@ export default class PocomathInstance { if (!imps) { throw new SyntaxError(`No implementations for ${name}`) } - const tf_imps = this._TFimps[name] - const meta_imps = this._metaTFimps[name] /* Collect the entries we know the types for */ const usableEntries = [] for (const entry of Object.entries(imps)) { let keep = true for (const type of typesOfSignature(entry[0])) { if (type in this.Types) continue - const [baseType] = splitTemplate(type) + const baseType = type.split('<')[0] if (baseType in this.Templates) continue keep = false break @@ -876,115 +567,70 @@ export default class PocomathInstance { * in the midst of being reassembled */ Object.defineProperty(this, name, {configurable: true, value: 'limbo'}) + const tf_imps = {} for (const [rawSignature, behavior] of usableEntries) { - if (behavior.explicit) { - if (!(behavior.resolved)) { - this._addTFimplementation(name, tf_imps, rawSignature, behavior) - tf_imps[rawSignature]._pocoSignature = rawSignature - behavior.resolved = true + /* Check if it's an ordinary non-template signature */ + let explicit = true + for (const type of typesOfSignature(rawSignature)) { + for (const word of type.split(/[<>]/)) { + if (this._templateParam(word)) { + explicit = false + break + } } + } + if (explicit) { + this._addTFimplementation(tf_imps, rawSignature, behavior) continue } /* It's a template, have to instantiate */ - /* First, find any upper bounds on the instantation */ - /* TODO: handle multiple upper bounds */ - upperBounds.lastIndex = 0 - let ubType = upperBounds.exec(rawSignature) - if (ubType) { - ubType = ubType[2] - if (!ubType in this.Types) { - throw new TypeError( - `Unknown type upper bound '${ubType}' in '${rawSignature}'`) - } - } /* First, add the known instantiations, gathering all types needed */ - if (ubType) behavior.needsInstantiations.add(ubType) - const nargs = typeListOfSignature(rawSignature).length + if (!('instantiations' in behavior)) { + behavior.instantiations = new Set() + } let instantiationSet = new Set() - const ubTypes = new Set() - if (!ubType) { - // Collect all upper-bound types for this signature - for (const othersig in imps) { - const otherNargs = typeListOfSignature(othersig).length - if (nargs !== otherNargs) { - // crude criterion that it won't match, that ignores - // rest args, but hopefully OK for prototype - continue - } - const thisUB = upperBounds.exec(othersig) - if (thisUB) ubTypes.add(thisUB[2]) - let basesig = othersig.replaceAll(templateCall, '') - if (basesig !== othersig) { - // A template - const testsig = substituteInSignature( - basesig, theTemplateParam, '') - if (testsig === basesig) { - // that is not also top-level - for (let templateType of typeListOfSignature(basesig)) { - if (templateType.slice(0,3) === '...') { - templateType = templateType.slice(3) - } - ubTypes.add(templateType) - } - } - } - } - } - for (const instType of behavior.needsInstantiations) { + for (const instType of behavior.instantiations) { instantiationSet.add(instType) - const otherTypes = - ubType ? this.subtypesOf(instType) : this._priorTypes[instType] - for (const other of otherTypes) { - if (!(this._atOrBelowSomeType(other, ubTypes))) { - instantiationSet.add(other) - } - } - } - /* Prevent other existing signatures from blocking use of top-level - * templates via conversions: - */ - let baseSignature = rawSignature.replaceAll(templateCall, '') - /* Any remaining template params are top-level */ - const signature = substituteInSignature( - baseSignature, theTemplateParam, UniversalType) - const hasTopLevel = (signature !== baseSignature) - if (!ubType && hasTopLevel) { - for (const othersig in imps) { - let basesig = othersig.replaceAll(templateCall, '') - const testsig = substituteInSignature( - basesig, theTemplateParam, '') - if (testsig !== basesig) continue // a top-level template - for (let othertype of typeListOfSignature(othersig)) { - if (othertype.slice(0,3) === '...') { - othertype = othertype.slice(3) - } - if (this.Types[othertype] === anySpec) continue - const testType = substituteInSignature( - othertype, theTemplateParam, '') - let otherTypeCollection = [othertype] - if (testType !== othertype) { - const [base] = splitTemplate(othertype) - otherTypeCollection = this._instantiationsOf[base] - } - for (const possibility of otherTypeCollection) { - for (const convtype of this._priorTypes[possibility]) { - if (this.isSubtypeOf(convtype, possibility)) continue - if (!(this._atOrBelowSomeType(convtype, ubTypes))) { - instantiationSet.add(convtype) - } - } - } - } + for (const other of this._priorTypes[instType]) { + instantiationSet.add(other) } } + for (const instType of instantiationSet) { - this._instantiateTemplateImplementation( - name, rawSignature, instType) + if (!(instType in this.Types)) continue + if (this.Types[instType] === anySpec) continue + const signature = + substituteInSig(rawSignature, theTemplateParam, instType) + /* Don't override an explicit implementation: */ + if (signature in imps) continue + const uses = new Set() + for (const dep of behavior.uses) { + if (this._templateParam(dep)) continue + uses.add(substituteInSig(dep, theTemplateParam, instType)) + } + const patch = (refs) => { + const innerRefs = {} + for (const dep of behavior.uses) { + if (this._templateParam(dep)) { + innerRefs[dep] = instType + } else { + const outerName = substituteInSig( + dep, theTemplateParam, instType) + innerRefs[dep] = refs[outerName] + } + } + return behavior.does(innerRefs) + } + this._addTFimplementation( + tf_imps, signature, {uses, does: patch}) } /* Now add the catchall signature */ - /* (Not needed if if it's a bounded template) */ - if (ubType) continue - if (behavior.resolved) continue + let templateCall = `<${theTemplateParam}>` + /* Relying here that the base of 'Foo' is 'Foo': */ + let baseSignature = rawSignature.replaceAll(templateCall, '') + /* Any remaining template params are top-level */ + const signature = substituteInSig( + 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: @@ -1003,315 +649,165 @@ export default class PocomathInstance { throw new SyntaxError( `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 - /* For return type annotation, we may have to fix this to - propagate the return type. At the moment we are just bagging - */ - const patch = () => { - const patchFunc = (...tfBundledArgs) => { - /* We unbundle the rest arg if there is one */ - let args = Array.from(tfBundledArgs) - const regLength = args.length - 1 - if (restParam) { - const restArgs = args.pop() - 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') { - console.log('INCOMPATIBLE ARGUMENTS are', args) - throw TypeError( - `In call to ${name}, ` - + 'incompatible template arguments:' - // + args.map(a => JSON.stringify(a)).join(', ') - // unfortunately barfs on bigints. Need a better - // formatter. I wish we could just use the one that - // console.log uses; is that accessible somehow? - + args.map(a => a.toString()).join(', ') - + ' of types ' + argTypes.join(', ') + argType) - } - argTypes.push(argType) - } - } - if (argTypes.length === 0) { - throw TypeError('Type inference failed for' + name) - } - let usedConversions = false - let instantiateFor = self.joinTypes(argTypes) - if (instantiateFor === 'any') { - usedConversions = true - instantiateFor = self.joinTypes(argTypes, usedConversions) - if (instantiateFor === 'any') { - let argDisplay = args.map(toString).join(', ') + const patch = (refs) => (...args) => { + /* We unbundle the rest arg if there is one */ + 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( - `In call to ${name}, no type unifies arguments ` - + argDisplay + '; of types ' + argTypes.toString() - + '; note each consecutive pair must unify to a ' - + 'supertype of at least one of them') + `Type inference failed for argument ${j} of ${name}`) } - } - const depth = instantiateFor.split('<').length - if (depth > self._maxDepthSeen) { - self._maxDepthSeen = depth - } - /* Generate the list of actual wanted types */ - const wantTypes = parTypes.map(type => substituteInSignature( - type, theTemplateParam, instantiateFor)) - const wantSig = wantTypes.join(',') - /* Now we have to add any actual types that are relevant - * to this invocation. Namely, that would be every formal - * parameter type in the invocation, with the parameter - * template instantiated by instantiateFor, and for all of - * instantiateFor's "prior types" - */ - for (j = 0; j < parTypes.length; ++j) { - if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) { - // actually used the param and is a template - const strippedType = parTypes[j].substr( - parTypes[j].lastIndexOf('.') + 1) - self._ensureTemplateTypes(strippedType, instantiateFor) + if (argType === 'any') { + throw TypeError( + `In call to ${name}, incompatible template arguments: ` + + args.map(a => JSON.stringify(a)).join(', ')) } + argTypes.push(argType) } - - /* Request the desired instantiation: */ - // But possibly since this resolution was grabbed, the proper - // instantiation has been added (like if there are multiple - // uses in the implementation of another method. - let whatToDo - if (!(instantiateFor in behavior.hasInstantiations)) { - const newImp = self._instantiateTemplateImplementation( - name, rawSignature, instantiateFor) - if (newImp) { - whatToDo = {fn: newImp, implementation: newImp} - } - self._invalidate(name) - } - const brandNewMe = self[name] - const betterToDo = self._typed.resolve(brandNewMe, args) - whatToDo = betterToDo || whatToDo - - // We can access return type information here - // And in particular, if it might be a template, we should try to - // instantiate it: - const returnType = returnTypeOf(whatToDo.fn, wantSig, self) - for (const possibility of returnType.split('|')) { - const instantiated = self._maybeInstantiate(possibility) - if (instantiated) { - const [tempBase] = splitTemplate(instantiated) - self._invalidateDependents(':' + tempBase) - } - } - if (whatToDo === lastWhatToDo) { - throw new Error( - `Infinite recursion in resolving ${name} called on ` - + args.map(x => - (typeof x === 'object' - ? JSON.stringify(x) - : x.toString()) - ).join(', ') - + ` inferred to be ${wantSig}`) - } - lastWhatToDo = whatToDo - const retval = whatToDo.implementation(...args) - lastWhatToDo = null - return retval } - Object.defineProperty( - patchFunc, 'name', {value: `${name}(${signature})`}) - patchFunc._pocoSignature = rawSignature - return patchFunc + 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 + behavior.instantiations.add(instantiateFor) + self._invalidate(name) + // And update refs because we now know the type we're instantiating + // for: + 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) } - Object.defineProperty( - patch, 'name', {value: `generate[${name}(${signature})]`}) - // TODO?: Decorate patch with a function that calculates the - // correct return type a priori. Deferring because unclear what - // aspects will be merged into typed-function. + // The actual uses value needs to be a set: + const outerUses = new Set(Object.values(simplifiedUses)) this._addTFimplementation( - name, meta_imps, signature, - {uses: new Set(), does: patch}, - behavior) - behavior.resolved = true + tf_imps, signature, {uses: outerUses, does: patch}) } - // Make sure we have all of the needed (template) types; and if they - // can't be added (because they have been instantiated too deep), - // ditch the signature: - const badSigs = new Set() - for (const sig in tf_imps) { - if (!tf_imps[sig].uses) { - throw new ReferenceError(`MONKEY WRENCH: ${name} ${sig}`) - } - for (const type of typeListOfSignature(sig)) { - if (this._maybeInstantiate(type) === undefined) { - badSigs.add(sig) - } - } - } - for (const badSig of badSigs) { - const imp = tf_imps[badSig] - delete tf_imps[badSig] - const fromBehavior = this._imps[name][imp._pocoSignature] - if (fromBehavior.explicit) { - fromBehavior.resolved = false - } else { - delete fromBehavior.hasInstantiations[imp._pocoInstance] - } - } - let tf - if (Object.keys(tf_imps).length > 0) { - tf = this._typed(name, tf_imps) - tf.fromInstance = this - tf.isMeta = false - } - let metaTF - if (Object.keys(meta_imps).length > 0) { - metaTF = this._metaTyped(name, meta_imps) - metaTF.fromInstance = this - metaTF.isMeta = true - } - this._meta[name] = metaTF - - tf = tf || metaTF + this._correctPartialSelfRefs(tf_imps) + const tf = this._typed(name, tf_imps) Object.defineProperty(this, name, {configurable: true, value: tf}) return tf } - /* Takes a type and a set of types and returns true if the type - * is a subtype of some type in the set. - */ - _atOrBelowSomeType(type, typeSet) { - if (typeSet.has(type)) return true - let belowSome = false - for (const anUB of typeSet) { - if (anUB in this.Templates) { - if (type.slice(0, anUB.length) === anUB) { - belowSome = true - break - } - } else { - if (this.isSubtypeOf(type, anUB)) { - belowSome = true - break - } - } - } - return belowSome - } - - /* Takes an arbitrary type and performs an instantiation if necessary. - * @param {string} type The type to instantiate - * @param {string | bool | undefined } - * Returns the name of the type if an instantiation occurred, false - * if the type was already present, and undefined if the type can't - * be satisfied (because it is not the name of a type or it is nested - * too deep. - */ - _maybeInstantiate(type) { - if (type.slice(0,3) === '...') { - type = type.slice(3) - } - if (!(type.includes('<'))) { - // Not a template, so just check if type exists - if (type in this.Types) return false // already there - return undefined // no such type - } - // it's a template type, turn it into a template and an arg - let [base, arg] = splitTemplate(type) - return this.instantiateTemplate(base, arg) - } - - /* Generate and include a template instantiation for operation name - * for the template signature templateSignature instantiated for - * instanceType, returning the resulting implementation. - */ - _instantiateTemplateImplementation(name, templateSignature, instanceType) { - if (!(instanceType in this.Types)) return undefined - if (this.Types[instanceType] === anySpec) return undefined - const imps = this._imps[name] - const behavior = imps[templateSignature] - if (instanceType in behavior.hasInstantiations) return undefined - const signature = substituteInSignature( - templateSignature, theTemplateParam, instanceType) - /* Don't override an explicit implementation: */ - if (signature in imps) return undefined - /* Don't go too deep */ - let maxdepth = 0 - for (const argType in typeListOfSignature(signature)) { - const depth = argType.split('<').length - if (depth > maxdepth) maxdepth = depth - } - if (maxdepth > this._maxDepthSeen + 1) return undefined - /* All right, go ahead and instantiate */ - const uses = new Set() - for (const dep of behavior.uses) { - if (this._templateParam(dep)) continue - uses.add(substituteInSignature(dep, theTemplateParam, instanceType)) - } - const patch = (refs) => { - const innerRefs = {} - for (const dep of behavior.uses) { - if (this._templateParam(dep)) { - innerRefs[dep] = instanceType - } else { - const outerName = substituteInSignature( - dep, theTemplateParam, instanceType) - innerRefs[dep] = refs[outerName] - } - } - return behavior.does(innerRefs) - } - const tf_imps = this._TFimps[name] - this._addTFimplementation( - name, tf_imps, signature, {uses, does: patch}, behavior, instanceType) - tf_imps[signature]._pocoSignature = templateSignature - tf_imps[signature]._pocoInstance = instanceType - behavior.hasInstantiations[instanceType] = signature - behavior.needsInstantiations.add(instanceType) // once we have it, keep it - return tf_imps[signature] - } - /* Adapts Pocomath-style behavior specification (uses, does) for signature - * to typed-function implementations and inserts the result into plain - * object imps + * to typed-function implementations and inserts the result into plain object + * imps */ - _addTFimplementation( - name, imps, signature, specificBehavior, fromImp, asInstance) - { - if (!fromImp) fromImp = specificBehavior - const {uses, does} = specificBehavior + _addTFimplementation(imps, signature, behavior) { + const {uses, does} = behavior if (uses.length === 0) { - const implementation = does() - implementation.uses = uses - implementation.fromInstance = this - implementation.fromBehavior = fromImp - implementation.instance = asInstance - // could do something with return type information here - imps[signature] = implementation + imps[signature] = does() return } const refs = {} let full_self_referential = false + let part_self_references = [] for (const dep of uses) { let [func, needsig] = dep.split(/[()]/) /* Safety check that can perhaps be removed: * Verify that the desired signature has been fully grounded: */ if (needsig) { - const trysig = substituteInSignature(needsig, theTemplateParam, '') + const trysig = substituteInSig(needsig, theTemplateParam, '') if (trysig !== needsig) { throw new Error( 'Attempt to add a template implementation: ' + @@ -1320,97 +816,104 @@ export default class PocomathInstance { } if (func === 'self') { if (needsig) { - /* We now resolve all specific-signature self references - * here, without resorting to the facility in typed-function: - */ - if (needsig in imps && typeof imps[needsig] == 'function') { - refs[dep] = imps[needsig] - continue + if (full_self_referential) { + throw new SyntaxError( + 'typed-function does not support mixed full and ' + + 'partial self-reference') } - const needTypes = typesOfSignature(needsig) - const mergedTypes = Object.assign( - {}, this.Types, this.Templates) - if (subsetOfKeys(needTypes, mergedTypes)) { - func = name // just resolve it in limbo - } else { - // uses an unknown type, so will get an undefined impl - console.log( - 'WARNING: partial self-reference for', name, 'to', - needsig, 'uses an unknown type') - refs[dep] = undefined - continue + if (subsetOfKeys(typesOfSignature(needsig), this.Types)) { + part_self_references.push(needsig) } } else { - full_self_referential = true - continue - } - } - if (this[func] === 'limbo') { - /* We are in the midst of bundling func (which may be ourself) */ - /* So the first thing we can do is try the tf_imps we are - * accumulating: - */ - if (needsig) { - const candidate = this.resolve(func, needsig) - if (typeof candidate === 'function') { - refs[dep] = candidate - continue + if (part_self_references.length) { + throw new SyntaxError( + 'typed-function does not support mixed full and ' + + 'partial self-reference') } + full_self_referential = true } - /* Either we need the whole function or the signature - * we need is not available yet, so we have to use - * an indirect reference to func. And given that, there's - * really no helpful way to extract a specific signature - */ - const self = this - const redirect = function () { // is this the most efficient? - return self[func].apply(this, arguments) + } else { + if (this[func] === 'limbo') { + /* We are in the midst of bundling func, so have to use + * an indirect reference to func. And given that, there's + * really no helpful way to extract a specific signature + */ + const self = this + refs[dep] = function () { // is this the most efficient? + return self[func].apply(this, arguments) + } + } else { + // can bundle up func, and grab its signature if need be + let destination = this[func] + if (needsig) { + destination = this._pocoresolve(func, needsig) + } + refs[dep] = destination } - Object.defineProperty(redirect, 'name', {value: func}) - Object.defineProperty(redirect, 'fromInstance', {value: this}) - refs[dep] = redirect - continue } - // can bundle up func, and grab its signature if need be - let destination = this[func] - if (needsig) { - destination = this.resolve(func, needsig) - } - if (!destination) { - // Unresolved reference. This is allowed so that - // you can bundle up just some portions of the library, - // but let's warn. - console.log( - 'WARNING: No definition found for dependency', - dep, 'needed by', name, '(', signature, ')') - } - refs[dep] = destination } if (full_self_referential) { imps[signature] = this._typed.referToSelf(self => { refs.self = self - const implementation = does(refs) - Object.defineProperty(implementation, 'name', {value: does.name}) - implementation.fromInstance = this - implementation.uses = uses - implementation.instance = asInstance - implementation.fromBehavior = fromImp - // What are we going to do with the return type info in here? - return implementation + return does(refs) }) - imps[signature].uses = uses - imps[signature].fromInstance = this - imps[signature].instance = asInstance - imps[signature].fromBehavior = fromImp return } - const implementation = does(refs) - implementation.fromInstance = this - implementation.fromBehavior = fromImp - implementation.instance = asInstance - implementation.uses = uses - // could do something with return type information here? - imps[signature] = implementation + if (part_self_references.length) { + /* There is an obstruction here. The list part_self_references + * 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) { + refs[`self(${part_self_references[i]})`] = impls[i] + } + return does(refs) + } + ) + } } /* This function analyzes the template and makes sure the @@ -1418,7 +921,8 @@ export default class PocomathInstance { * in the instance. */ _ensureTemplateTypes(template, type) { - const [base, arg] = splitTemplate(template) + let [base, arg] = template.split('<', 2) + arg = arg.slice(0,-1) if (!arg) { throw new Error( 'Implementation error in _ensureTemplateTypes', template, type) @@ -1432,41 +936,29 @@ export default class PocomathInstance { } const resultingTypes = new Set() for (const iType of instantiations) { - const resultType = this.instantiateTemplate(base, iType) + const resultType = this._maybeAddTemplateType(base, iType) if (resultType) resultingTypes.add(resultType) } return resultingTypes } - /* Maybe add the instantiation of template type base with argument type - * instantiator to the Types of this instance, if it hasn't happened - * already. - * Returns the name of the type if added, false if it was already there, - * and undefined if the type is declined (because of being nested too deep). + /* 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. */ - instantiateTemplate = Returns('void', function(base, instantiator) { - const depth = instantiator.split('<').length - if (depth > this._maxDepthSeen ) { - // don't bother with types much deeper thant we have seen - return undefined - } + _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 = {} + const newTypeSpec = {refines: base} const maybeFrom = {} const template = this.Templates[base].spec if (!template) { throw new Error( - `Implementor error in instantiateTemplate(${base}, ${instantiator})`) + `Implementor error in _maybeAddTemplateType ${base} ${instantiator}`) } const instantiatorSpec = this.Types[instantiator] - if (instantiatorSpec.refines) { - this.instantiateTemplate(base, instantiatorSpec.refines) - // Assuming our templates are covariant, I guess - newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>` - } let beforeTypes = [] if (instantiatorSpec.before) { beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`) @@ -1474,35 +966,36 @@ export default class PocomathInstance { if (template.before) { for (const beforeTmpl of template.before) { beforeTypes.push( - substituteInSignature(beforeTmpl, theTemplateParam, instantiator)) + substituteInSig(beforeTmpl, theTemplateParam, instantiator)) } } if (beforeTypes.length > 0) { newTypeSpec.before = beforeTypes } - const templateTest = template.test(this._typeTests[instantiator]) - newTypeSpec.test = x => (template.base(x) && templateTest(x)) + newTypeSpec.test = template.test(this._typeTests[instantiator]) if (template.from) { for (let source in template.from) { - const instSource = substituteInSignature( + const instSource = substituteInSig( source, theTemplateParam, instantiator) - const testSource = substituteInSignature( - instSource, templateFromParam, instantiator) - const usesFromParam = (testSource !== instSource) + 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 = substituteInSignature( + const finalSource = substituteInSig( instSource, templateFromParam, iFrom) maybeFrom[finalSource] = template.from[source]( instantiatorSpec.from[iFrom]) } - if (testSource !== wantsType) { // subtypes handled with refines - // Assuming all templates are covariant here, I guess... - for (const subType of this._subtypes[instantiator]) { - const finalSource = substituteInSignature( - instSource, templateFromParam, subType) - maybeFrom[finalSource] = template.from[source](x => x) - } + // 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] @@ -1514,12 +1007,10 @@ export default class PocomathInstance { newTypeSpec.from = maybeFrom } this.installType(wantsType, newTypeSpec) - this._instantiationsOf[base].add(wantsType) return wantsType - }) + } - _findSubtypeImpl(name, imps, neededSig, raw = false) { - const detemplate = !raw + _findSubtypeImpl(imps, neededSig) { if (neededSig in imps) return neededSig let foundSig = false const typeList = typeListOfSignature(neededSig) @@ -1527,51 +1018,35 @@ export default class PocomathInstance { const otherTypeList = typeListOfSignature(otherSig) if (typeList.length !== otherTypeList.length) continue let allMatch = true - let paramBound = UniversalType for (let k = 0; k < typeList.length; ++k) { let myType = typeList[k] let otherType = otherTypeList[k] if (otherType === theTemplateParam) { - if (detemplate) otherTypeList[k] = paramBound - otherType = paramBound + otherTypeList[k] = 'ground' + otherType = 'ground' } - if (otherType === restTemplateParam) { - if (detemplate) otherTypeList[k] = `...${paramBound}` - otherType = paramBound + if (otherType === '...T') { + otherTypeList[k] = '...ground' + otherType = 'ground' } - const adjustedOtherType = otherType.replaceAll(templateCall, '') + const adjustedOtherType = otherType.replaceAll( + `<${theTemplateParam}>`, '') if (adjustedOtherType !== otherType) { - if (detemplate) otherTypeList[k] = adjustedOtherType + otherTypeList[k] = adjustedOtherType otherType = adjustedOtherType } if (myType.slice(0,3) === '...') myType = myType.slice(3) if (otherType.slice(0,3) === '...') otherType = otherType.slice(3) - const otherBound = upperBounds.exec(otherType) - if (otherBound) { - paramBound = otherBound[2] - otherType = paramBound - if (detemplate) { - otherTypeList[k] = otherBound[1].replaceAll( - theTemplateParam, paramBound) - } - } if (otherType === 'any') continue - if (otherType === UniversalType) continue - if (myType === otherType) continue - if (otherType in this.Templates) { - const [myBase] = splitTemplate(myType) - if (myBase === otherType) continue - if (this.instantiateTemplate(otherType, myType)) { - let dummy - dummy = this[name] // for side effects - return this._findSubtypeImpl(name, this._imps[name], neededSig, raw) - } - } + if (otherType === 'ground') continue if (!(otherType in this.Types)) { allMatch = false break } - if (this.isSubtypeOf(myType, otherType)) continue + if (myType === otherType + || this._subtypes[otherType].has(myType)) { + continue + } allMatch = false break } @@ -1583,156 +1058,17 @@ export default class PocomathInstance { return foundSig } - _pocoFindSignature(name, sig, typedFunction) { - if (!this._typed.isTypedFunction(typedFunction)) { - typedFunction = this[name] - } - const haveTF = this._typed.isTypedFunction(typedFunction) - && !(typedFunction.isMeta) - if (haveTF) { - // First try a direct match - let result - try { - result = this._typed.findSignature(typedFunction, sig, {exact: true}) - } catch { - } - if (result) return result - // Next, look ourselves but with subtypes: - const wantTypes = typeListOfSignature(sig) - for (const [implSig, details] - of typedFunction._typedFunctionData.signatureMap) { - let allMatched = true - const implTypes = typeListOfSignature(implSig) - if (implTypes.length > wantTypes.length) { - // Not enough arguments for that implementation - continue - } - for (let i = 0; i < wantTypes.length; ++i) { - const implIndex = Math.min(i, implTypes.length - 1) - let implType = implTypes[implIndex] - if (implIndex < i) { - if (implType.slice(0,3) !== '...') { - // ran out of arguments in impl - allMatched = false - break - } - } - if (implType.slice(0,3) === '...') { - implType = implType.slice(3) - } - const hasMatch = implType.split('|').some( - t => (wantTypes[i] === t || this.isSubtypeOf(wantTypes[i], t))) - if (!hasMatch) { - allMatched = false - break - } - } - if (allMatched) return details - } - } - if (!(this._imps[name])) return undefined - const foundsig = this._findSubtypeImpl(name, this._imps[name], sig, 'raw') - if (foundsig) { - if (haveTF) { - try { - return this._typed.findSignature(typedFunction, foundsig) - } catch { - } - } - const instantiationMatcher = - '^' - + substituteInSignature(foundsig, theTemplateParam, '(.*)') - .replaceAll(UniversalType, '(.*)') - + '$' - const instanceMatch = sig.match(instantiationMatcher) - let possibleInstantiator = false - if (instanceMatch) { - possibleInstantiator = instanceMatch[1] - for (let i = 2; i < instanceMatch.length; ++i) { - if (possibleInstantiator !== instanceMatch[i]) { - possibleInstantiator = false - break - } - } - } - if (possibleInstantiator) { - const behavior = this._imps[name][foundsig] - let newInstance - if (behavior) { - if (!(possibleInstantiator in behavior.hasInstantiations)) { - newInstance = this._instantiateTemplateImplementation( - name, foundsig, possibleInstantiator) - } else { - // OK, so we actually have the instantiation. Let's get it - newInstance = this._TFimps[name][sig] - } - // But we may not have taken advantage of conversions - this._invalidate(name) - const tryAgain = this[name] - let betterInstance - if (this._typed.isTypedFunction(tryAgain)) { - betterInstance = this._typed.findSignature(tryAgain, sig) - } - if (betterInstance) { - newInstance = betterInstance - } else { - newInstance = { - fn: newInstance, - implementation: newInstance - } - } - if (newInstance) return newInstance - } - } - const catchallSig = this._findSubtypeImpl(name, this._imps[name], sig) - if (catchallSig !== foundsig) { - try { - return this._metaTyped.findSignature( - this._meta[name], catchallSig) - } catch { - } - } - // We have an implementation but not a typed function. Do the best - // we can: - const restoredSig = foundsig.replaceAll('ground', theTemplateParam) - const foundImpl = this._imps[name][restoredSig] - const needs = {} - for (const dep of foundImpl.uses) { - const [base, sig] = dep.split(/[()]/) - if (sig) { - needs[dep] = this.resolve(base, sig) - } else { - needs[dep] = this[dep] - } - } - const pseudoImpl = foundImpl.does(needs) - pseudoImpl.fromInstance = this - return {fn: pseudoImpl, implementation: pseudoImpl} - } - // Hmm, no luck. Make sure bundle is up-to-date and retry: + _pocoresolve(name, sig) { + const typedfunc = this[name] let result = undefined - typedFunction = this[name] try { - result = this._typed.findSignature(typedFunction, sig) + result = this._typed.find(typedfunc, sig, {exact: true}) } catch { } - return result + 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) } - /* Returns a function that implements the operation with the given name - * when called with the given signature. The optional third argument is - * the typed function that provides the operation name, which can be - * passed in for efficiency if it is already available. - */ - resolve = Returns('function', function (name, sig, typedFunction) { - if (!this._typed.isTypedFunction(typedFunction)) { - typedFunction = this[name] - } - const result = this._pocoFindSignature(name, sig, typedFunction) - if (result) return result.implementation - // total punt, revert to typed-function resolution on every call; - // hopefully this happens rarely: - return typedFunction - }) - } diff --git a/src/core/Returns.mjs b/src/core/Returns.mjs deleted file mode 100644 index 211babd..0000000 --- a/src/core/Returns.mjs +++ /dev/null @@ -1,34 +0,0 @@ -/* Annotate a function with its return type */ - -/* Unfortunately JavaScript is missing a way to cleanly clone a function - * object, see https://stackoverflow.com/questions/1833588 - */ - -const clonedFrom = Symbol('the original function this one was cloned from') -function cloneFunction(fn) { - const behavior = fn[clonedFrom] || fn // don't nest clones - const theClone = function () { return behavior.apply(this, arguments) } - Object.assign(theClone, fn) - theClone[clonedFrom] = body - Object.defineProperty( - theClone, 'name', {value: fn.name, configurable: true }) - return theClone -} - -export function Returns(type, fn) { - if ('returns' in fn) fn = cloneFunction(fn) - fn.returns = type - return fn -} - -export function returnTypeOf(fn, signature, pmInstance) { - const typeOfReturns = typeof fn.returns - if (typeOfReturns === 'undefined') return 'any' - if (typeOfReturns === 'string') return fn.returns - // not sure if we will need a function to determine the return type, - // but allow it for now: - return fn.returns(signature, pmInstance) -} - -export default Returns - diff --git a/src/core/extractors.mjs b/src/core/extractors.mjs index f463cd1..0db3c0f 100644 --- a/src/core/extractors.mjs +++ b/src/core/extractors.mjs @@ -6,7 +6,7 @@ export function dependencyExtractor(destinationSet) { return new Proxy({}, { get: (target, property) => { destinationSet.add(property) - return {checkingDependency: true} + return {} } }) } diff --git a/src/core/utils.mjs b/src/core/utils.mjs index 9ae9aab..db164dd 100644 --- a/src/core/utils.mjs +++ b/src/core/utils.mjs @@ -8,8 +8,6 @@ export function subsetOfKeys(set, obj) { /* Returns a list of the types mentioned in a typed-function signature */ export function typeListOfSignature(signature) { - signature = signature.trim() - if (!signature) return [] return signature.split(',').map(s => s.trim()) } diff --git a/src/generic/Types/adapted.mjs b/src/generic/Types/adapted.mjs deleted file mode 100644 index 6c54fc5..0000000 --- a/src/generic/Types/adapted.mjs +++ /dev/null @@ -1,91 +0,0 @@ -import PocomathInstance from '../../core/PocomathInstance.mjs' -import Returns from '../../core/Returns.mjs' - -/* creates a PocomathInstance incorporating a new numeric type encapsulated - * as a class. (This instance can the be `install()ed` in another to add the - * type so created.) - * - * @param {string} name The name of the new type - * @param {class} Thing The class implementing the new type - * @param {object} overrides Patches to the auto-generated adaptation - */ -export default function adapted(name, Thing, overrides) { - const thing = new PocomathInstance('Adapted Thing') - const test = overrides.isa || Thing.isa || (x => x instanceof Thing) - thing.installType(name, { - test, - from: overrides.from || {}, - before: overrides.before || [], - refines: overrides.refines || undefined - }) - - // Build the operations for Thing - const operations = {} - // first a creator function, with name depending on the name of the thing: - const creatorName = overrides.creatorName || name.toLowerCase() - const creator = overrides[creatorName] - ? overrides[creatorName][''] - : Thing[creatorName] - ? (Thing[creatorName]) - : ((...args) => new Thing(...args)) - const defaultCreatorImps = { - '': () => Returns(name, () => creator()), - '...any': () => Returns(name, args => creator(...args)) - } - defaultCreatorImps[name] = () => Returns(name, x => x) // x.clone(x)? - operations[creatorName] = overrides[creatorName] || defaultCreatorImps - - // We make the default instance, just as a place to check for methods - const instance = overrides.instance || creator() - - // Now adapt the methods to typed-function: - const unaryOps = { - abs: 'abs', - ceiling: 'ceil', - floor: 'floor', - invert: 'inverse', - round: 'round', - sqrt: 'sqrt', - negate: 'neg' - } - const binaryOps = { - add: ['add', name], - compare: ['compare', name], - divide: ['div', name], - equalTT: ['equals', 'boolean'], - gcd: ['gcd', name], - lcm: ['lcm', name], - mod: ['mod', name], - multiply: ['mul', name], - subtract: ['sub', name] - } - for (const [mathname, standardname] of Object.entries(unaryOps)) { - if (standardname in instance) { - operations[mathname] = {} - operations[mathname][name] = () => Returns(name, t => t[standardname]()) - } - } - operations.zero = {} - operations.zero[name] = () => Returns(name, t => creator()) - operations.one = {} - operations.one[name] = () => Returns(name, t => creator(1)) - operations.conjugate = {} - operations.conjugate[name] = () => Returns(name, t => t) // or t.clone() ?? - - const binarySignature = `${name},${name}` - for (const [mathname, spec] of Object.entries(binaryOps)) { - if (spec[0] in instance) { - operations[mathname] = {} - operations[mathname][binarySignature] = () => Returns( - spec[1], (t,u) => t[spec[0]](u)) - } - } - if ('operations' in overrides) { - Object.assign(operations, overrides.operations) - } - - thing.install(operations) - return thing -} - -export {adapted} diff --git a/src/generic/abs.mjs b/src/generic/abs.mjs deleted file mode 100644 index ff20ee8..0000000 --- a/src/generic/abs.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import Returns from '../core/Returns.mjs' -export const abs = { - T: ({ - T, - 'smaller(T,T)': lt, - 'negate(T)': neg, - 'zero(T)': zr - }) => Returns(T, t => (smaller(t, zr(t)) ? neg(t) : t)) -} diff --git a/src/generic/absquare.mjs b/src/generic/absquare.mjs deleted file mode 100644 index 052131d..0000000 --- a/src/generic/absquare.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import Returns from '../core/Returns.mjs' - -export const absquare = { - T: ({ - T, - 'square(T)': sq, - 'abs(T)': abval - }) => Returns(T, t => sq(abval(t))) -} diff --git a/src/generic/all.mjs b/src/generic/all.mjs index 45bd9d0..19ba165 100644 --- a/src/generic/all.mjs +++ b/src/generic/all.mjs @@ -1,26 +1,2 @@ -import {adapted} from './Types/adapted.mjs' -import Fraction from 'fraction.js/bigfraction.js' -import Returns from '../core/Returns.mjs' - export * from './arithmetic.mjs' export * from './relational.mjs' - -export const fraction = adapted('Fraction', Fraction, { - before: ['Complex'], - from: {number: n => new Fraction(n)}, - operations: { - compare: { - 'Fraction,Fraction': () => Returns( - 'Fraction', (f,g) => new Fraction(f.compare(g))) - }, - mod: { - 'Fraction,Fraction': () => Returns('Fraction', (n,d) => { - // patch for "mathematician's modulus" - // OK to use full public API of Fraction here - const fmod = n.mod(d) - if (fmod.s === -1n) return fmod.add(d.abs()) - return fmod - }) - } - } -}) diff --git a/src/generic/arithmetic.mjs b/src/generic/arithmetic.mjs index 51f0da9..00faddb 100644 --- a/src/generic/arithmetic.mjs +++ b/src/generic/arithmetic.mjs @@ -2,18 +2,14 @@ import {reducingOperation} from './reducingOperation.mjs' export * from './Types/generic.mjs' -export {abs} from './abs.mjs' -export {absquare} from './absquare.mjs' export const add = reducingOperation -export {divide} from './divide.mjs' export const gcd = reducingOperation export {identity} from './identity.mjs' export {lcm} from './lcm.mjs' export {mean} from './mean.mjs' export {mod} from './mod.mjs' export const multiply = reducingOperation -export {quotient} from './quotient.mjs' -export {roundquotient} from './roundquotient.mjs' +export {divide} from './divide.mjs' export {sign} from './sign.mjs' export {sqrt} from './sqrt.mjs' export {square} from './square.mjs' diff --git a/src/generic/divide.mjs b/src/generic/divide.mjs index ab1e893..1aee89b 100644 --- a/src/generic/divide.mjs +++ b/src/generic/divide.mjs @@ -1,10 +1,7 @@ -import Returns from '../core/Returns.mjs' - export const divide = { 'T,T': ({ - T, 'multiply(T,T)': multT, 'invert(T)': invT - }) => Returns(T, (x, y) => multT(x, invT(y))) + }) => (x, y) => multT(x, invT(y)) } diff --git a/src/generic/gcdType.mjs b/src/generic/gcdType.mjs index ee86b50..1ca16ab 100644 --- a/src/generic/gcdType.mjs +++ b/src/generic/gcdType.mjs @@ -1,5 +1,3 @@ -import Returns from '../core/Returns.mjs' - /* Note we do not use a template here so that we can explicitly control * which types this is instantiated for, namely the "integer" types, and * not simply allow Pocomath to generate instances for any type it encounters. @@ -9,14 +7,14 @@ export default function(type) { const producer = refs => { const modder = refs[`mod(${type},${type})`] const zeroTester = refs[`isZero(${type})`] - return Returns(type, (a,b) => { + return (a,b) => { while (!zeroTester(b)) { const r = modder(a,b) a = b b = r } return a - }) + } } const retval = {} retval[`${type},${type}`] = producer diff --git a/src/generic/identity.mjs b/src/generic/identity.mjs index fb3853e..2422d2f 100644 --- a/src/generic/identity.mjs +++ b/src/generic/identity.mjs @@ -1,11 +1,3 @@ -import Returns from '../core/Returns.mjs' - -export function identityType(type) { - return () => Returns(type, x => x) +export function identity(x) { + return x } - -export function identitySubTypes(type) { - return ({T}) => Returns(T, x => x) -} - -export const identity = {T: ({T}) => Returns(T, x => x)} diff --git a/src/generic/lcm.mjs b/src/generic/lcm.mjs index 26bfbf8..04e78b5 100644 --- a/src/generic/lcm.mjs +++ b/src/generic/lcm.mjs @@ -1,12 +1,10 @@ -import Returns from '../core/Returns.mjs' import {reducingOperation} from './reducingOperation.mjs' export const lcm = { 'T,T': ({ - T, 'multiply(T,T)': multT, 'quotient(T,T)': quotT, 'gcd(T,T)': gcdT - }) => Returns(T, (a,b) => multT(quotT(a, gcdT(a,b)), b)) + }) => (a,b) => multT(quotT(a, gcdT(a,b)), b) } Object.assign(lcm, reducingOperation) diff --git a/src/generic/mean.mjs b/src/generic/mean.mjs index 58cbc19..d12c21b 100644 --- a/src/generic/mean.mjs +++ b/src/generic/mean.mjs @@ -1,8 +1,3 @@ -import Returns from '../core/Returns.mjs' export const mean = { - '...T': ({ - T, - add, - 'divide(T,NumInt)': div - }) => Returns(T, args => div(add(...args), args.length)) + '...any': ({add, divide}) => args => divide(add(...args), args.length) } diff --git a/src/generic/mod.mjs b/src/generic/mod.mjs index e1b5ec6..84af4e6 100644 --- a/src/generic/mod.mjs +++ b/src/generic/mod.mjs @@ -1,10 +1,7 @@ -import Returns from '../core/Returns.mjs' - export const mod = { 'T,T': ({ - T, 'subtract(T,T)': subT, 'multiply(T,T)': multT, 'quotient(T,T)': quotT - }) => Returns(T, (a,m) => subT(a, multT(m, quotT(a,m)))) + }) => (a,m) => subT(a, multT(m, quotT(a,m))) } diff --git a/src/generic/quotient.mjs b/src/generic/quotient.mjs deleted file mode 100644 index 521bd2a..0000000 --- a/src/generic/quotient.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import Returns from '../core/Returns.mjs' - -export const quotient = { - 'T,T': ({ - T, - 'floor(T)': flr, - 'divide(T,T)': div - }) => Returns(T, (n,d) => flr(div(n,d))) -} diff --git a/src/generic/reducingOperation.mjs b/src/generic/reducingOperation.mjs index 3c256f2..101a8ec 100644 --- a/src/generic/reducingOperation.mjs +++ b/src/generic/reducingOperation.mjs @@ -1,16 +1,12 @@ -import Returns from '../core/Returns.mjs' export * from './Types/generic.mjs' export const reducingOperation = { - 'undefined': () => Returns('undefined', u => u), - 'undefined,...any': () => Returns('undefined', (u, rest) => u), - 'any,undefined': () => Returns('undefined', (x, u) => u), - 'undefined,undefined': () => Returns('undefined', (u,v) => u), - T: ({T}) => Returns(T, x => x), - // Unfortunately the type language of Pocomath is not (yet?) expressive - // enough to properly type the full reduction signature here: + 'undefined': () => u => u, + 'undefined,...any': () => (u, rest) => u, + 'any,undefined': () => (x, u) => u, + any: () => x => x, 'any,any,...any': ({ self - }) => Returns('any', (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a)) + }) => (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a) } diff --git a/src/generic/relational.mjs b/src/generic/relational.mjs index 72de34c..939ae19 100644 --- a/src/generic/relational.mjs +++ b/src/generic/relational.mjs @@ -1,83 +1,67 @@ -import Returns from '../core/Returns.mjs' - export const compare = { - 'undefined,undefined': () => Returns('NumInt', () => 0) + 'undefined,undefined': () => () => 0 } export const isZero = { - 'undefined': () => Returns('boolean', u => u === 0), - T: ({ - T, - 'equal(T,T)': eq, - 'zero(T)': zr - }) => Returns('boolean', t => eq(t, zr(t))) + 'undefined': () => u => u === 0 } export const equal = { - 'any,any': ({ - equalTT, - joinTypes, - Templates, - typeOf - }) => Returns('boolean', (x,y) => { + '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 - }) => Returns('boolean', (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 = { - 'any,any': ({equal}) => Returns('boolean', (x,y) => !(equal(x,y))) + 'any,any': ({equal}) => (x,y) => !(equal(x,y)) } export const larger = { 'T,T': ({ 'compare(T,T)': cmp, - 'one(T)' : uno, - 'equalTT(T,T)' : eq - }) => Returns('boolean', (x,y) => eq(cmp(x,y), uno(y))) + 'one(T)' : uno + }) => (x,y) => cmp(x,y) === uno(y) } export const largerEq = { 'T,T': ({ 'compare(T,T)': cmp, 'one(T)' : uno, - 'isZero(T)' : isZ, - 'equalTT(T,T)': eq - }) => Returns('boolean', (x,y) => { + 'isZero(T)' : isZ + }) => (x,y) => { const c = cmp(x,y) - return isZ(c) || eq(c, uno(y)) - }) + return isZ(c) || c === uno(y) + } } export const smaller = { 'T,T': ({ 'compare(T,T)': cmp, 'one(T)' : uno, - 'isZero(T)' : isZ, - unequal - }) => Returns('boolean', (x,y) => { + 'isZero(T)' : isZ + }) => (x,y) => { const c = cmp(x,y) - return !isZ(c) && unequal(c, uno(y)) - }) + return !isZ(c) && c !== uno(y) + } } export const smallerEq = { 'T,T': ({ 'compare(T,T)': cmp, - 'one(T)' : uno, - unequal - }) => Returns('boolean', (x,y) => unequal(cmp(x,y), uno(y))) + 'one(T)' : uno + }) => (x,y) => cmp(x,y) !== uno(y) } diff --git a/src/generic/roundquotient.mjs b/src/generic/roundquotient.mjs deleted file mode 100644 index 9c2ba2b..0000000 --- a/src/generic/roundquotient.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import Returns from '../core/Returns.mjs' - -export const roundquotient = { - 'T,T': ({ - T, - 'round(T)': rnd, - 'divide(T,T)':div - }) => Returns(T, (n,d) => rnd(div(n,d))) -} diff --git a/src/generic/sign.mjs b/src/generic/sign.mjs index cec73cd..769e2c9 100644 --- a/src/generic/sign.mjs +++ b/src/generic/sign.mjs @@ -1,9 +1,3 @@ -import Returns from '../core/Returns.mjs' - export const sign = { - T: ({ - T, - 'compare(T,T)': cmp, - 'zero(T)': Z - }) => Returns(T, x => cmp(x, Z(x))) + T: ({'compare(T,T)': cmp, 'zero(T)': Z}) => x => cmp(x, Z(x)) } diff --git a/src/generic/sqrt.mjs b/src/generic/sqrt.mjs index faea759..21aa1d5 100644 --- a/src/generic/sqrt.mjs +++ b/src/generic/sqrt.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/generic.mjs' -export const sqrt = {undefined: () => Returns('undefined', () => undefined)} +export const sqrt = {undefined: () => () => undefined} diff --git a/src/generic/square.mjs b/src/generic/square.mjs index 2619c29..53fd6c2 100644 --- a/src/generic/square.mjs +++ b/src/generic/square.mjs @@ -1,6 +1,3 @@ -import {Returns, returnTypeOf} from '../core/Returns.mjs' - export const square = { - T: ({'multiply(T,T)': multT}) => Returns( - returnTypeOf(multT), x => multT(x,x)) + T: ({'multiply(T,T)': multT}) => x => multT(x,x) } diff --git a/src/generic/subtract.mjs b/src/generic/subtract.mjs index 35dab22..b048d0c 100644 --- a/src/generic/subtract.mjs +++ b/src/generic/subtract.mjs @@ -1,9 +1,3 @@ -import Returns from '../core/Returns.mjs' - export const subtract = { - 'T,T': ({ - T, - 'add(T,T)': addT, - 'negate(T)': negT - }) => Returns(T, (x,y) => addT(x, negT(y))) + 'T,T': ({'add(T,T)': addT, 'negate(T)': negT}) => (x,y) => addT(x, negT(y)) } diff --git a/src/number/abs.mjs b/src/number/abs.mjs index 80b45d8..66ede16 100644 --- a/src/number/abs.mjs +++ b/src/number/abs.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' -export const abs = {'T:number': ({T}) => Returns(T, n => Math.abs(n))} +export const abs = {number: () => n => Math.abs(n)} diff --git a/src/number/absquare.mjs b/src/number/absquare.mjs deleted file mode 100644 index 31a417d..0000000 --- a/src/number/absquare.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import Returns from '../core/Returns.mjs' -export * from './Types/number.mjs' - -/* Absolute value squared */ -export const absquare = { - 'T:number': ({T, 'square(T)': sqn}) => Returns(T, n => sqn(n)) -} diff --git a/src/number/add.mjs b/src/number/add.mjs index 5c363d1..7d79637 100644 --- a/src/number/add.mjs +++ b/src/number/add.mjs @@ -1,8 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' -export const add = { - // Note the below assumes that all subtypes of number that will be defined - // are closed under addition! - 'T:number,T': ({T}) => Returns(T, (m,n) => m+n) -} +export const add = {'number,number': () => (m,n) => m+n} diff --git a/src/number/cbrt.mjs b/src/number/cbrt.mjs deleted file mode 100644 index af7587b..0000000 --- a/src/number/cbrt.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import Returns from '../core/Returns.mjs' -export * from './Types/number.mjs' - -/* Returns just the real cube root, following mathjs implementation */ -export const cbrt = { - number: ({'negate(number)': neg}) => Returns('number', x => { - if (x === 0) return x - const negate = x < 0 - if (negate) x = neg(x) - let result = x - if (isFinite(x)) { - result = Math.exp(Math.log(x) / 3) - result = (x / (result * result) + (2 * result)) / 3 - } - if (negate) return neg(result) - return result - }) -} - diff --git a/src/number/compare.mjs b/src/number/compare.mjs index c4b1c26..4dc865b 100644 --- a/src/number/compare.mjs +++ b/src/number/compare.mjs @@ -1,5 +1,3 @@ -import Returns from '../core/Returns.mjs' - /* Lifted from mathjs/src/utils/number.js */ /** * Minimum number added to one that makes the result different than one @@ -50,6 +48,5 @@ function nearlyEqual (x, y, epsilon) { export const compare = { 'number,number': ({ config - }) => Returns( - 'NumInt', (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1)) + }) => (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1) } diff --git a/src/number/invert.mjs b/src/number/invert.mjs index 780ad72..4eabe2f 100644 --- a/src/number/invert.mjs +++ b/src/number/invert.mjs @@ -1,5 +1,3 @@ -import Returns from '../core/Returns.mjs' - export * from './Types/number.mjs' -export const invert = {number: () => Returns('number', n => 1/n)} +export const invert = {number: () => n => 1/n} diff --git a/src/number/isZero.mjs b/src/number/isZero.mjs index 0209daa..ac98ce2 100644 --- a/src/number/isZero.mjs +++ b/src/number/isZero.mjs @@ -1,6 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' -export const isZero = { - 'T:number': () => Returns('boolean', n => n === 0) -} +export const isZero = {number: () => n => n === 0} diff --git a/src/number/multiply.mjs b/src/number/multiply.mjs index 5951f22..80573d1 100644 --- a/src/number/multiply.mjs +++ b/src/number/multiply.mjs @@ -1,5 +1,3 @@ -import Returns from '../core/Returns.mjs' - export * from './Types/number.mjs' -export const multiply = {'T:number,T': ({T}) => Returns(T, (m,n) => m*n)} +export const multiply = {'number,number': () => (m,n) => m*n} diff --git a/src/number/native.mjs b/src/number/native.mjs index ad2de12..d095574 100644 --- a/src/number/native.mjs +++ b/src/number/native.mjs @@ -1,14 +1,12 @@ import gcdType from '../generic/gcdType.mjs' -import {identitySubTypes} from '../generic/identity.mjs' +import {identity} from '../generic/identity.mjs' export * from './Types/number.mjs' export {abs} from './abs.mjs' -export {absquare} from './absquare.mjs' export {add} from './add.mjs' -export {cbrt} from './cbrt.mjs' export {compare} from './compare.mjs' -export const conjugate = {'T:number': identitySubTypes('number')} +export const conjugate = {number: () => identity} export const gcd = gcdType('NumInt') export {invert} from './invert.mjs' export {isZero} from './isZero.mjs' diff --git a/src/number/negate.mjs b/src/number/negate.mjs index f2336c7..82e27d0 100644 --- a/src/number/negate.mjs +++ b/src/number/negate.mjs @@ -1,6 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' -export const negate = { - 'T:number': ({T}) => Returns(T, n => -n) -} +export const negate = {number: () => n => -n} diff --git a/src/number/one.mjs b/src/number/one.mjs index e38d0dc..5726468 100644 --- a/src/number/one.mjs +++ b/src/number/one.mjs @@ -1,5 +1,3 @@ -import Returns from '../core/Returns.mjs' - export * from './Types/number.mjs' -export const one = {number: () => Returns('NumInt', () => 1)} +export const one = {number: () => () => 1} diff --git a/src/number/quotient.mjs b/src/number/quotient.mjs index b307709..d86b832 100644 --- a/src/number/quotient.mjs +++ b/src/number/quotient.mjs @@ -1,10 +1,8 @@ -import Returns from '../core/Returns.mjs' - export * from './Types/number.mjs' export const quotient = { - 'T:number,T': () => Returns('NumInt', (n,d) => { + 'number,number': () => (n,d) => { if (d === 0) return d return Math.floor(n/d) - }) + } } diff --git a/src/number/roundquotient.mjs b/src/number/roundquotient.mjs index 8c4c519..401d499 100644 --- a/src/number/roundquotient.mjs +++ b/src/number/roundquotient.mjs @@ -1,10 +1,8 @@ -import Returns from '../core/Returns.mjs' - export * from './Types/number.mjs' export const roundquotient = { - 'number,number': () => Returns('NumInt', (n,d) => { + 'number,number': () => (n,d) => { if (d === 0) return d return Math.round(n/d) - }) + } } diff --git a/src/number/sqrt.mjs b/src/number/sqrt.mjs index 2e5e734..f6cc459 100644 --- a/src/number/sqrt.mjs +++ b/src/number/sqrt.mjs @@ -1,18 +1,14 @@ -import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' export const sqrt = { - number: ({ - config, - 'complex(number,number)': cplx, - 'negate(number)': neg}) => { - if (config.predictable || !cplx) { - return Returns('number', n => isNaN(n) ? NaN : Math.sqrt(n)) - } - return Returns('number|Complex', n => { - if (isNaN(n)) return NaN - if (n >= 0) return Math.sqrt(n) - return cplx(0, Math.sqrt(neg(n))) - }) + number: ({config, complex, 'self(Complex)': complexSqrt}) => { + if (config.predictable || !complexSqrt) { + return n => isNaN(n) ? NaN : Math.sqrt(n) } + return n => { + if (isNaN(n)) return NaN + if (n >= 0) return Math.sqrt(n) + return complexSqrt(complex(n)) + } + } } diff --git a/src/number/zero.mjs b/src/number/zero.mjs index 5e3e3a3..40ac2fb 100644 --- a/src/number/zero.mjs +++ b/src/number/zero.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' -export const zero = {number: () => Returns('NumInt', () => 0)} +export const zero = {number: () => () => 0} diff --git a/src/ops/choose.mjs b/src/ops/choose.mjs index dd22dc2..c285dc7 100644 --- a/src/ops/choose.mjs +++ b/src/ops/choose.mjs @@ -1,13 +1,11 @@ -import Returns from '../core/Returns.mjs' - -/* Note this is _not_ a good algorithm for computing binomial coefficients, +/* Note this is not a good algorithm for computing binomial coefficients, * it's just for demonstration purposes */ export const choose = { - 'NumInt,NumInt': ({factorial}) => Returns( - 'NumInt', (n,k) => Number(factorial(n) / (factorial(k)*factorial(n-k)))), + 'NumInt,NumInt': ({factorial}) => (n,k) => Number( + factorial(n) / (factorial(k)*factorial(n-k))), 'bigint,bigint': ({ factorial - }) => Returns('bigint', (n,k) => factorial(n) / (factorial(k)*factorial(n-k))) + }) => (n,k) => factorial(n) / (factorial(k)*factorial(n-k)) } diff --git a/src/ops/factorial.mjs b/src/ops/factorial.mjs index b1154f3..bb07047 100644 --- a/src/ops/factorial.mjs +++ b/src/ops/factorial.mjs @@ -1,15 +1,8 @@ -import Returns from '../core/Returns.mjs' - -/* Plain functions are OK, too, and they can be decorated with a return type - * just like an implementation. - */ -const factorial = Returns('bigint', function factorial(n) { +export function factorial(n) { n = BigInt(n) let prod = 1n for (let i = n; i > 1n; --i) { prod *= i } return prod -}) - -export {factorial} +} diff --git a/src/ops/floor.mjs b/src/ops/floor.mjs index 3754dcb..e8897e8 100644 --- a/src/ops/floor.mjs +++ b/src/ops/floor.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' import {Complex} from '../complex/Types/Complex.mjs' /* Note we don't **export** any types here, so that only the options @@ -6,28 +5,21 @@ import {Complex} from '../complex/Types/Complex.mjs' */ export const floor = { - /* Because Pocomath isn't part of typed-function, nor does it have access - * to the real typed-function parse, we unfortunately can't coalesce the - * first several implementations into one entry with type - * `bigint|NumInt|GaussianInteger` because then they couldn't - * be separately activated - */ - bigint: () => Returns('bigint', x => x), - NumInt: () => Returns('NumInt', x => x), - 'Complex': () => Returns('Complex', x => x), + bigint: () => x => x, + NumInt: () => x => x, // Because Pocomath isn't part of typed-function, or + GaussianInteger: () => x => x, // at least have access to the real + // typed-function parse, we unfortunately can't coalesce these into one + // entry with type `bigint|NumInt|GaussianInteger` because they couldn't + // be separately activated then - number: ({'equalTT(number,number)': eq}) => Returns('NumInt', n => { + number: ({'equalTT(number,number)': eq}) => n => { if (eq(n, Math.round(n))) return Math.round(n) return Math.floor(n) - }), + }, - 'Complex': Complex.promoteUnary['Complex'], + Complex: Complex.promoteUnary.Complex, // OK to include a type totally not in Pocomath yet, it'll never be // activated. - BigNumber: ({ - 'round(BigNumber)': rnd, - 'equal(BigNumber,BigNumber)': eq - }) => Returns('BigNumber', x => eq(x,round(x)) ? round(x) : x.floor()) - + Fraction: ({quotient}) => f => quotient(f.n, f.d), } diff --git a/src/tuple/Types/Tuple.mjs b/src/tuple/Types/Tuple.mjs index 4ff6685..0c6c0ae 100644 --- a/src/tuple/Types/Tuple.mjs +++ b/src/tuple/Types/Tuple.mjs @@ -1,24 +1,25 @@ /* A template type representing a homeogeneous tuple of elements */ import PocomathInstance from '../../core/PocomathInstance.mjs' -import {Returns, returnTypeOf} from '../../core/Returns.mjs' const Tuple = new PocomathInstance('Tuple') - +// 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', { - // A test that "defines" the "base type", which is not really a type - // (only fully instantiated types are added to the universe) - base: t => t && typeof t === 'object' && 'elts' in t && Array.isArray(t.elts), - // The template portion of the test; it takes the test for T as - // input and returns the test for an entity _that already passes - // the base test_ to be a Tuple: - test: testT => t => t.elts.every(testT), - // And we need there to be a way to determine the (instantiation) - // type of an tuple (that has already passed the base test): + // We are assuming that any 'Type' 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)), - // Conversions. Parametrized conversions are only invoked for types - // U such that there is already a conversion from U to T, and that - // conversion is passed as an input, and we have to return the conversion - // function from the indicated template in terms of U to Tuple: + // 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 + 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: from: { 'Tuple': convert => tu => ({elts: tu.elts.map(convert)}), // Here since there is no U it's a straight conversion: @@ -31,69 +32,48 @@ Tuple.installType('Tuple', { }) Tuple.promoteUnary = { - 'Tuple': ({ - 'self(T)': me, - tuple - }) => { - const compType = me.fromInstance.joinTypes( - returnTypeOf(me).split('|'), 'convert') - return Returns( - `Tuple<${compType}>`, t => tuple(...(t.elts.map(x => me(x))))) - } + 'Tuple': ({'self(T)': me, tuple}) => t => tuple(...(t.elts.map(me))) } Tuple.promoteBinaryUnary = { - 'Tuple,Tuple': ({'self(T,T)': meB, 'self(T)': meU, tuple}) => { - const compTypes = returnTypeOf(meB).split('|').concat( - returnTypeOf(meU).split('|')) - const compType = meU.fromInstance.joinTypes(compTypes, 'convert') - return Returns(`Tuple<${compType}>`, (s,t) => { - let i = -1 - let result = [] - while (true) { - i += 1 - if (i < s.elts.length) { - if (i < t.elts.length) result.push(meB(s.elts[i], t.elts[i])) - else result.push(meU(s.elts[i])) - continue - } - if (i < t.elts.length) result.push(meU(t.elts[i])) - else break + 'Tuple,Tuple': ({'self(T,T)': meB, 'self(T)': meU, tuple}) => (s,t) => { + let i = -1 + let result = [] + while (true) { + i += 1 + if (i < s.elts.length) { + if (i < t.elts.length) result.push(meB(s.elts[i], t.elts[i])) + else result.push(meU(s.elts[i])) + continue } - return tuple(...result) - }) + if (i < t.elts.length) result.push(meU(t.elts[i])) + else break + } + return tuple(...result) } } Tuple.promoteBinary = { - 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => { - const compType = meB.fromInstance.joinTypes( - returnTypeOf(meB).split('|')) - return Returns(`Tuple<${compType}>`, (s,t) => { - const lim = Math.max(s.elts.length, t.elts.length) - const result = [] - for (let i = 0; i < lim; ++i) { - result.push(meB(s.elts[i], t.elts[i])) - } - return tuple(...result) - }) + 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => (s,t) => { + const lim = Math.max(s.elts.length, t.elts.length) + const result = [] + for (let i = 0; i < lim; ++i) { + result.push(meB(s.elts[i], t.elts[i])) + } + return tuple(...result) } } Tuple.promoteBinaryStrict = { - 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => { - const compType = meB.fromInstance.joinTypes( - returnTypeOf(meB).split('|')) - return Returns(`Tuple<${compType}>`, (s,t) => { - if (s.elts.length !== t.elts.length) { - throw new RangeError('Tuple length mismatch') // get name of self ?? - } - const result = [] - for (let i = 0; i < s.elts.length; ++i) { - result.push(meB(s.elts[i], t.elts[i])) - } - return tuple(...result) - }) + 'Tuple,Tuple': ({'self(T,T)': meB, tuple}) => (s,t) => { + if (s.elts.length !== t.elts.length) { + throw new RangeError('Tuple length mismatch') // get name of self ?? + } + const result = [] + for (let i = 0; i < s.elts.length; ++i) { + result.push(meB(s.elts[i], t.elts[i])) + } + return tuple(...result) } } diff --git a/src/tuple/equalTT.mjs b/src/tuple/equalTT.mjs index 557ee2d..1606410 100644 --- a/src/tuple/equalTT.mjs +++ b/src/tuple/equalTT.mjs @@ -1,16 +1,11 @@ -import Returns from '../core/Returns.mjs' - export * from './Types/Tuple.mjs' export const equalTT = { - 'Tuple,Tuple': ({ - 'self(T,T)': me, - 'length(Tuple)': len - }) => Returns('boolean', (s,t) => { + 'Tuple,Tuple': ({'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 - }) + } } diff --git a/src/tuple/isZero.mjs b/src/tuple/isZero.mjs index a48e92a..9375277 100644 --- a/src/tuple/isZero.mjs +++ b/src/tuple/isZero.mjs @@ -1,10 +1,7 @@ -import Returns from '../core/Returns.mjs' - export {Tuple} from './Types/Tuple.mjs' export const isZero = { - 'Tuple': ({'self(T)': me}) => Returns( - 'boolean', t => t.elts.every(e => me(e))) + 'Tuple': ({'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` diff --git a/src/tuple/length.mjs b/src/tuple/length.mjs index 4df2c74..f3e8f2d 100644 --- a/src/tuple/length.mjs +++ b/src/tuple/length.mjs @@ -1,4 +1,3 @@ -import Returns from '../core/Returns.mjs' export {Tuple} from './Types/Tuple.mjs' -export const length = {'Tuple': () => Returns('NumInt', t => t.elts.length)} +export const length = {Tuple: () => t => t.elts.length} diff --git a/src/tuple/tuple.mjs b/src/tuple/tuple.mjs index 8467176..893b54d 100644 --- a/src/tuple/tuple.mjs +++ b/src/tuple/tuple.mjs @@ -1,10 +1,6 @@ -import Returns from '../core/Returns.mjs' - export {Tuple} from './Types/Tuple.mjs' /* The purpose of the template argument is to ensure that all of the args * are convertible to the same type. */ -export const tuple = { - '...T': ({T}) => Returns(`Tuple<${T}>`, args => ({elts: args})) -} +export const tuple = {'...T': () => args => ({elts: args})} diff --git a/test/_pocomath.mjs b/test/_pocomath.mjs index c4a5c28..1d7d1e9 100644 --- a/test/_pocomath.mjs +++ b/test/_pocomath.mjs @@ -16,60 +16,13 @@ describe('The default full pocomath instance "math"', () => { assert.strictEqual(math.typeOf(-1.5), 'number') assert.strictEqual(math.typeOf(-42n), 'bigint') assert.strictEqual(math.typeOf(undefined), 'undefined') - assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex') - assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex') - }) - - it('can determine the return types of operations', () => { - assert.strictEqual(math.returnTypeOf('negate', 'number'), 'number') - assert.strictEqual(math.returnTypeOf('negate', 'NumInt'), 'NumInt') - math.negate(math.complex(1.2, 2.8)) // TODO: make this call unnecessary - assert.strictEqual( - math.returnTypeOf('negate', 'Complex'), 'Complex') - assert.strictEqual(math.returnTypeOf('add', 'number,number'), 'number') - assert.strictEqual(math.returnTypeOf('add', 'NumInt,NumInt'), 'NumInt') - assert.strictEqual(math.returnTypeOf('add', 'NumInt,number'), 'number') - assert.strictEqual(math.returnTypeOf('add', 'number,NumInt'), 'number') - assert.deepStrictEqual( // TODO: ditto - math.add(3, math.complex(2.5, 1)), math.complex(5.5, 1)) - assert.strictEqual( - math.returnTypeOf('add', 'Complex,NumInt'), 'Complex') - // The following is not actually what we want, but the Pocomath type - // language isn't powerful enough at this point to capture the true - // return type: - assert.strictEqual( - math.returnTypeOf('add', 'number,NumInt,Complex'), 'any') - assert.strictEqual( - math.returnTypeOf('chain', 'bigint'), 'Chain') - assert.strictEqual( - math.returnTypeOf('returnTypeOf', 'string,string'), 'string') - assert.strictEqual( - math.returnTypeOf('conjugate', 'bigint'), 'bigint') - assert.strictEqual( - math.returnTypeOf('gcd', 'bigint,bigint'), 'bigint') - math.identity(math.fraction(3,5)) // TODO: ditto - assert.strictEqual(math.returnTypeOf('identity', 'Fraction'), 'Fraction') - assert.strictEqual( - math.returnTypeOf('quotient', 'bigint,bigint'), 'bigint') - math.abs(math.complex(2,1)) //TODO: ditto - assert.strictEqual( - math.returnTypeOf('abs','Complex'), 'number') - math.multiply(math.quaternion(1,1,1,1), math.quaternion(1,-1,1,-1)) // dit - const quatType = math.returnTypeOf( - 'quaternion', 'NumInt,NumInt,NumInt,NumInt') - assert.strictEqual(quatType, 'Complex>') - assert.strictEqual( - math.returnTypeOf('multiply', quatType + ',' + quatType), quatType) - assert.strictEqual(math.returnTypeOf('isZero', 'NumInt'), 'boolean') - assert.strictEqual( - math.returnTypeOf('roundquotient', 'NumInt,number'), 'NumInt') - assert.strictEqual( - math.returnTypeOf('factorial', 'NumInt'), 'bigint') + assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger') + assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex') }) it('can subtract numbers', () => { assert.strictEqual(math.subtract(12, 5), 7) - assert.throws(() => math.subtract(3n, 1.5), 'TypeError') + //assert.strictEqual(math.subtract(3n, 1.5), 1.5) }) it('can add numbers', () => { @@ -152,9 +105,11 @@ describe('The default full pocomath instance "math"', () => { it('calculates multi-way gcds and lcms', () => { assert.strictEqual(math.gcd(30,105,42), 3) - const gaussianLCM = math.lcm( - math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)) - assert.strictEqual(math.associate(gaussianLCM, math.complex(1n,3n)), true) + assert.ok( + math.associate( + math.lcm( + math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)), + math.complex(1n,3n))) }) }) diff --git a/test/complex/_all.mjs b/test/complex/_all.mjs index a14872e..e486fc3 100644 --- a/test/complex/_all.mjs +++ b/test/complex/_all.mjs @@ -39,30 +39,16 @@ describe('complex', () => { }) it('checks for equality', () => { - assert.ok(math.equal(math.complex(3, 0), 3)) - assert.ok(math.equal(math.complex(3, 2), math.complex(3, 2))) + assert.ok(math.equal(math.complex(3,0), 3)) + assert.ok(math.equal(math.complex(3,2), math.complex(3, 2))) assert.ok(!(math.equal(math.complex(45n, 3n), math.complex(45n, -3n)))) assert.ok(!(math.equal(math.complex(45n, 3n), 45n))) }) - it('tests for reality', () => { - assert.ok(math.isReal(math.complex(3, 0))) - assert.ok(!(math.isReal(math.complex(3, 2)))) - }) - it('computes gcd', () => { assert.deepStrictEqual( math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)), math.complex(4n, 5n)) - // And now works for NumInt, too! - assert.deepStrictEqual( - math.gcd(math.complex(53,56), math.complex(47, -13)), - math.complex(4, 5)) - // But properly fails for general complex - assert.throws( - () => math.gcd(math.complex(5.3,5.6), math.complex(4.7, -1.3)), - TypeError - ) }) it('computes floor', () => { @@ -73,24 +59,4 @@ describe('complex', () => { assert.strictEqual(math.floor(gi), gi) // literally a no-op }) - it('performs rudimentary quaternion calculations', () => { - const q0 = math.quaternion(1, 0, 1, 0) - const q1 = math.quaternion(1, 0.5, 0.5, 0.75) - assert.deepStrictEqual( - q1, - math.complex(math.complex(1, 0.5), math.complex(0.5, 0.75))) - assert.deepStrictEqual( - math.add(q0,q1), - math.quaternion(2, 0.5, 1.5, 0.75)) - assert.deepStrictEqual( - math.multiply(q0, q1), - math.quaternion(0.5, 1.25, 1.5, 0.25)) - assert.deepStrictEqual( - math.multiply(q0, math.quaternion(2, 1, 0.1, 0.1)), - math.quaternion(1.9, 1.1, 2.1, -0.9)) - math.absquare(math.complex(1.25, 2.5)) //HACK: need absquare(Complex) - assert.strictEqual(math.abs(q0), Math.sqrt(2)) - assert.strictEqual(math.abs(q1), Math.sqrt(33)/4) - }) - }) diff --git a/test/complex/_polynomialRoot.mjs b/test/complex/_polynomialRoot.mjs deleted file mode 100644 index 22ad90c..0000000 --- a/test/complex/_polynomialRoot.mjs +++ /dev/null @@ -1,63 +0,0 @@ -import assert from 'assert' -import * as approx from '../../tools/approx.mjs' -import math from '../../src/pocomath.mjs' - -describe('polynomialRoot', () => { - it('should solve a linear equation with real coefficients', function () { - assert.deepEqual(math.polynomialRoot(6, 3), math.tuple(-2)) - assert.deepEqual( - math.polynomialRoot(math.complex(-3, 2), 2), - math.tuple(math.complex(1.5, -1))) - assert.deepEqual( - math.polynomialRoot(math.complex(3, 1), math.complex(-1, -1)), - math.tuple(math.complex(2, -1))) - }) - // Should be safe now to capture the functions: - const complex = math.complex - const pRoot = math.polynomialRoot - const tup = math.tuple - it('should solve a quadratic equation with a double root', function () { - assert.deepEqual(pRoot(4, 4, 1), tup(-2)) - assert.deepEqual( - pRoot(complex(0, 2), complex(2, 2), 1), tup(complex(-1, -1))) - }) - it('should solve a quadratic with two distinct roots', function () { - assert.deepEqual(pRoot(-3, 2, 1), tup(1, -3)) - assert.deepEqual(pRoot(-2, 0, 1), tup(math.sqrt(2), -math.sqrt(2))) - assert.deepEqual( - pRoot(4, 2, 1), - tup(complex(-1, math.sqrt(3)), complex(-1, -math.sqrt(3)))) - assert.deepEqual( - pRoot(complex(3, 1), -3, 1), tup(complex(1, 1), complex(2, -1))) - }) - it('should solve a cubic with a triple root', function () { - assert.deepEqual(pRoot(8, 12, 6, 1), tup(-2)) - assert.deepEqual( - pRoot(complex(-2, 11), complex(9, -12), complex(-6, 3), 1), - tup(complex(2, -1))) - }) - it('should solve a cubic with one simple and one double root', function () { - assert.deepEqual(pRoot(4, 0, -3, 1), tup(-1, 2)) - assert.deepEqual( - pRoot(complex(9, 9), complex(15, 6), complex(7, 1), 1), - tup(complex(-1, -1), -3)) - assert.deepEqual( - pRoot(complex(0, 6), complex(6, 8), complex(5, 2), 1), - tup(-3, complex(-1, -1))) - assert.deepEqual( - pRoot(complex(2, 6), complex(8, 6), complex(5, 1), 1), - tup(complex(-3, 1), complex(-1, -1))) - }) - it('should solve a cubic with three distinct roots', function () { - approx.deepEqual(pRoot(6, 11, 6, 1), tup(-3, -1, -2)) - approx.deepEqual( - pRoot(-1, -2, 0, 1), - tup(-1, (1 + math.sqrt(5)) / 2, (1 - math.sqrt(5)) / 2)) - approx.deepEqual( - pRoot(1, 1, 1, 1), - tup(-1, complex(0, -1), complex(0, 1))) - approx.deepEqual( - pRoot(complex(0, -10), complex(8, 12), complex(-6, -3), 1), - tup(complex(1, 1), complex(3, 1), complex(2, 1))) - }) -}) diff --git a/test/core/_utils.mjs b/test/core/_utils.mjs deleted file mode 100644 index 3976517..0000000 --- a/test/core/_utils.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import assert from 'assert' -import * as utils from '../../src/core/utils.mjs' - -describe('typeListOfSignature', () => { - it('returns an empty list for the empty signature', () => { - assert.deepStrictEqual(utils.typeListOfSignature(''), []) - }) -}) diff --git a/test/custom.mjs b/test/custom.mjs index b6fd111..6a399b0 100644 --- a/test/custom.mjs +++ b/test/custom.mjs @@ -3,14 +3,12 @@ import math from '../src/pocomath.mjs' import PocomathInstance from '../src/core/PocomathInstance.mjs' import * as numbers from '../src/number/all.mjs' import * as numberAdd from '../src/number/add.mjs' -import * as numberZero from '../src/number/zero.mjs' import {add as genericAdd} from '../src/generic/arithmetic.mjs' import * as complex from '../src/complex/all.mjs' import * as complexAdd from '../src/complex/add.mjs' import * as complexNegate from '../src/complex/negate.mjs' import * as complexComplex from '../src/complex/complex.mjs' import * as bigintAdd from '../src/bigint/add.mjs' -import * as bigintZero from '../src/bigint/zero.mjs' import * as concreteSubtract from '../src/generic/subtract.concrete.mjs' import * as genericSubtract from '../src/generic/subtract.mjs' import extendToComplex from '../src/complex/extendToComplex.mjs' @@ -56,9 +54,9 @@ describe('A custom instance', () => { math.complex(-5, -1)) // And now floor has been activated for Complex as well, since the type // is present - const fracComplex = math.complex(1.9, 0) - const intComplex = math.complex(1) - assert.deepStrictEqual(pm.floor(fracComplex), intComplex) + assert.deepStrictEqual( + pm.floor(math.complex(1.9, 0)), + math.complex(1)) // And the chain functions refresh themselves: assert.deepStrictEqual( pm.chain(5).add(pm.chain(0).complex(7).value).value, math.complex(5,7)) @@ -67,11 +65,10 @@ describe('A custom instance', () => { it("can defer definition of (even used) types", () => { const dt = new PocomathInstance('Deferred Types') dt.install(numberAdd) - dt.install(numberZero) // for promoting numbers to complex, to fill in im dt.install({times: { 'number,number': () => (m,n) => m*n, - 'Complex,Complex': ({'complex(T,T)': cplx}) => (w,z) => { - return cplx(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re) + 'Complex,Complex': ({complex}) => (w,z) => { + return complex(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re) } }}) // complex type not present but should still be able to add numbers: @@ -85,7 +82,6 @@ describe('A custom instance', () => { it("can selectively import in cute ways", async function () { const cherry = new PocomathInstance('cherry') cherry.install(numberAdd) - cherry.install(numberZero) // for complex promotion await extendToComplex(cherry) cherry.install({add: genericAdd}) /* Now we have an instance that supports addition for number and complex @@ -128,15 +124,19 @@ describe('A custom instance', () => { inst.install(complexAdd) inst.install(complexComplex) inst.install(bigintAdd) - inst.install(bigintZero) // for complex promotion assert.strictEqual( inst.typeMerge(6n, inst.complex(3n, 2n)), - 'Merge to Complex') + 'Merge to GaussianInteger') assert.strictEqual( inst.typeMerge(3, inst.complex(4.5,2.1)), - 'Merge to Complex') - assert.throws( - () => inst.typeMerge(3, inst.complex(3n)), TypeError) + '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) }) diff --git a/test/generic/_all.mjs b/test/generic/_all.mjs deleted file mode 100644 index 94829a4..0000000 --- a/test/generic/_all.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import assert from 'assert' -import math from '../../src/pocomath.mjs' - -describe('generic', () => { - it('calculates mean', () => { - assert.strictEqual(math.mean(1,2.5,3.25,4.75), 2.875) - assert.strictEqual( - math.returnTypeOf('mean', 'number,number,number,number'), - 'number' - ) - }) - it('compares things', () => { - assert.strictEqual(math.larger(7n, 3n), true) - assert.strictEqual( - math.returnTypeOf('larger', 'bigint,bigint'), 'boolean') - assert.strictEqual(math.smallerEq(7.2, 3), false) - assert.strictEqual( - math.returnTypeOf('smallerEq', 'number,NumInt'), 'boolean') - }) -}) diff --git a/test/generic/fraction.mjs b/test/generic/fraction.mjs deleted file mode 100644 index f1fcd97..0000000 --- a/test/generic/fraction.mjs +++ /dev/null @@ -1,101 +0,0 @@ -import assert from 'assert' -import math from '../../src/pocomath.mjs' -import Fraction from 'fraction.js/bigfraction.js' - -describe('fraction', () => { - const half = new Fraction(1/2) - const tf = new Fraction(3, 4) - let zero // will fill in during a test - const one = new Fraction(1) - - it('supports typeOf', () => { - assert.strictEqual(math.typeOf(half), 'Fraction') - }) - - it('can be built', () => { - zero = math.fraction() - assert.deepStrictEqual(zero, new Fraction(0)) - assert.deepStrictEqual(math.fraction(1/2), half) - assert.strictEqual(math.fraction(half), half) // maybe it should be a clone? - assert.strictEqual(math.fraction(9, 16).valueOf(), 9/16) - assert.strictEqual(math.fraction(9n, 16n).valueOf(), 9/16) - }) - - it('has abs and sign', () => { - assert.deepStrictEqual(math.abs(math.fraction('-1/2')), half) - assert.deepStrictEqual(math.sign(math.negate(tf)), math.negate(one)) - }) - - it('can add and multiply', () => { - assert.strictEqual(math.add(half, 1).valueOf(), 1.5) - assert.strictEqual(math.multiply(2, half).valueOf(), 1) - }) - - it('can subtract and divide', () => { - assert.strictEqual(math.subtract(half,tf).valueOf(), -0.25) - assert.strictEqual(math.divide(tf,half).valueOf(), 1.5) - }) - - it('computes mod', () => { - assert.strictEqual(math.mod(tf, half).valueOf(), 0.25) - assert.strictEqual(math.mod(tf, math.negate(half)).valueOf(), 0.25) - assert.strictEqual(math.mod(math.negate(tf), half).valueOf(), 0.25) - assert.strictEqual( - math.mod(math.negate(tf), math.negate(half)).valueOf(), - 0.25) - assert.deepStrictEqual( - math.mod(math.fraction(-1, 3), half), - math.fraction(1, 6)) - }) - - it('supports conjugate', () => { - assert.strictEqual(math.conjugate(half), half) - }) - - it('can compare fractions', () => { - assert.deepStrictEqual(math.compare(tf, half), one) - assert.strictEqual(math.equal(half, math.fraction("2/4")), true) - assert.strictEqual(math.smaller(half, tf), true) - assert.strictEqual(math.larger(half, tf), false) - assert.strictEqual(math.smallerEq(tf, math.fraction(0.75)), true) - assert.strictEqual(math.largerEq(tf, half), true) - assert.strictEqual(math.unequal(half, tf), true) - assert.strictEqual(math.isZero(math.zero(tf)), true) - assert.strictEqual(math.isZero(half), false) - }) - - it('computes gcd and lcm', () => { - assert.strictEqual(math.gcd(half,tf).valueOf(), 0.25) - assert.strictEqual(math.lcm(half,tf).valueOf(), 1.5) - }) - - it('computes additive and multiplicative inverses', () => { - assert.strictEqual(math.negate(half).valueOf(), -0.5) - assert.deepStrictEqual(math.invert(tf), math.fraction('4/3')) - }) - - it('computes integer parts and quotients', () => { - assert.deepStrictEqual(math.floor(tf), zero) - assert.deepStrictEqual(math.round(tf), one) - assert.deepStrictEqual(math.ceiling(half), one) - assert.deepStrictEqual(math.quotient(tf, half), one) - assert.deepStrictEqual( - math.roundquotient(math.fraction(7/8), half), - math.multiply(2,math.one(tf))) - }) - - it('has no sqrt (although that should be patched)', () => { - assert.throws(() => math.sqrt(math.fraction(9/16)), TypeError) - }) - - it('but it can square', () => { - assert.deepStrictEqual(math.square(tf), math.fraction(9/16)) - }) - - it('knows the types of its operations', () => { - assert.deepStrictEqual( - math.returnTypeOf('ceiling', 'Fraction'), 'Fraction') - assert.deepStrictEqual( - math.returnTypeOf('multiply', 'Fraction,Fraction'), 'Fraction') - }) -}) diff --git a/test/tuple/_native.mjs b/test/tuple/_native.mjs index 8a9b34c..2cf56d1 100644 --- a/test/tuple/_native.mjs +++ b/test/tuple/_native.mjs @@ -8,12 +8,14 @@ describe('tuple', () => { it('does not allow unification by converting consecutive arguments', () => { assert.throws(() => math.tuple(3, 5.2, 2n), /TypeError.*unif/) + // Hence, the order matters in a slightly unfortunate way, + // but I think being a little ragged in these edge cases is OK: assert.throws( () => math.tuple(3, 2n, math.complex(5.2)), /TypeError.*unif/) - assert.throws( - () => math.tuple(3, math.complex(2n), 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', () => { @@ -54,9 +56,6 @@ describe('tuple', () => { assert.deepStrictEqual( math.subtract(math.tuple(3n,4n,5n), math.tuple(2n,1n,0n)), math.tuple(1n,3n,5n)) - assert.deepStrictEqual( - math.returnTypeOf('subtract', 'Tuple,Tuple'), - 'Tuple') assert.throws( () => math.subtract(math.tuple(5,6), math.tuple(7)), /RangeError/) @@ -107,16 +106,9 @@ describe('tuple', () => { }) it('supports sqrt', () => { - const mixedTuple = math.tuple(2, math.complex(0,2), 1.5) assert.deepStrictEqual( - mixedTuple, - math.tuple(math.complex(2), math.complex(0,2), math.complex(1.5))) - assert.strictEqual( - math.returnTypeOf('tuple', 'NumInt, Complex, number'), - 'Tuple>') - assert.deepStrictEqual(math.sqrt(math.tuple(4,-4,2.25)), mixedTuple) - assert.strictEqual( - math.returnTypeOf('sqrt', 'Tuple'), 'Tuple>') + math.sqrt(math.tuple(4,-4,2.25)), + math.tuple(2, math.complex(0,2), 1.5)) }) }) diff --git a/tools/approx.mjs b/tools/approx.mjs deleted file mode 100644 index cab5483..0000000 --- a/tools/approx.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import assert from 'assert' - -export const epsilon = 1e-12 - -const isNumber = entity => (typeof entity === 'number') - -export function equal(a, b) { - if (isNumber(a) && isNumber(b)) { - if (a === b) return true - if (isNaN(a)) return assert.strictEqual(a.toString(), b.toString()) - const message = `${a} ~= ${b} (to ${epsilon})` - if (a === 0) return assert.ok(Math.abs(b) < epsilon, message) - if (b === 0) return assert.ok(Math.abs(a) < epsilon, message) - const diff = Math.abs(a - b) - const maxDiff = Math.abs(epsilon * Math.max(Math.abs(a), Math.abs(b))) - return assert.ok(diff <= maxDiff, message) - } - return assert.strictEqual(a, b) -} - -export function deepEqual(a, b) { - if (Array.isArray(a) && Array.isArray(b)) { - const alen = a.length - assert.strictEqual(alen, b.length, `${a} ~= ${b}`) - for (let i = 0; i < alen; ++i) deepEqual(a[i], b[i]) - return true - } - if (typeof a === 'object' && typeof b === 'object') { - for (const prop in a) { - if (a.hasOwnProperty(prop)) { - assert.ok( - b.hasOwnProperty(prop), `a[${prop}] = ${a[prop]} ~= ${b[prop]}`) - deepEqual(a[prop], b[prop]) - } - } - - for (const prop in b) { - if (b.hasOwnProperty(prop)) { - assert.ok( - a.hasOwnProperty(prop), `${a[prop]} ~= ${b[prop]} = b[${prop}]`) - } - } - return true - } - return equal(a, b) -}