Merge pull request 'refactor(Complex): Now a template type!' (#46) from template_complex into main

Reviewed-on: #46
This commit is contained in:
Glen Whitney 2022-08-06 15:42:19 +00:00
commit 40619b9a2e
25 changed files with 240 additions and 157 deletions

View File

@ -2,14 +2,18 @@ export * from './Types/bigint.mjs'
import isqrt from 'bigint-isqrt'
export const sqrt = {
bigint: ({config, complex, 'self(Complex)': complexSqrt}) => {
bigint: ({
config,
'complex(bigint,bigint)': cplx,
'negate(bigint)': neg
}) => {
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 b => isqrt(b)
}
if (!complexSqrt) {
if (!cplx) {
return b => {
if (b >= 0n) {
const trial = isqrt(b)
@ -19,12 +23,16 @@ export const sqrt = {
}
}
return b => {
if (b >= 0n) {
const trial = isqrt(b)
if (trial * trial === b) return trial
return undefined
if (b === undefined) return undefined
let real = true
if (b < 0n) {
b = neg(b)
real = false
}
return complexSqrt(complex(b))
const trial = isqrt(b)
if (trial * trial !== b) return undefined
if (real) return trial
return cplx(0n, trial)
}
}
}

View File

@ -1,30 +1,27 @@
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')
// Base type that should generally not be used directly
Complex.installType('Complex', {
test: isComplex,
from: {
number: x => ({re: x, im: 0})
}
test: z => z && typeof z === 'object' && 're' in z && 'im' in z
})
Complex.installType('GaussianInteger', {
test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint',
refines: 'Complex',
// Now the template type: Complex numbers are actually always homeogeneous
// in their component types.
Complex.installType('Complex<T>', {
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
test: testT => z => testT(z.re) && testT(z.im),
from: {
bigint: x => ({re: x, im: 0n})
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<U>': convert => cu => ({re: convert(cu.re), im: convert(cu.im)})
}
})
Complex.promoteUnary = {
Complex: ({self,complex}) => z => complex(self(z.re), self(z.im))
'Complex<T>': ({'self(T)': me, complex}) => z => complex(me(z.re), me(z.im))
}
export {Complex}

View File

@ -1,5 +1,8 @@
export * from './Types/Complex.mjs'
export const abs = {
Complex: ({sqrt, 'absquare(Complex)': absq}) => z => sqrt(absq(z))
'Complex<T>': ({
'sqrt(T)': sqt,
'absquare(Complex<T>)': absq
}) => z => sqt(absq(z))
}

View File

@ -1,5 +1,8 @@
export * from './Types/Complex.mjs'
export const absquare = {
Complex: ({add, square}) => z => add(square(z.re), square(z.im))
'Complex<T>': ({
'add(T,T)': plus,
'square(T)': sqr
}) => z => plus(sqr(z.re), sqr(z.im))
}

View File

@ -1,22 +1,8 @@
export * from './Types/Complex.mjs'
export const add = {
/* 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))
'Complex<T>,Complex<T>': ({
'self(T,T)': me,
'complex(T,T)': cplx
}) => (w,z) => cplx(me(w.re, z.re), me(w.im, z.im))
}

View File

@ -2,16 +2,16 @@ export * from './Types/Complex.mjs'
/* Returns true if w is z multiplied by a complex unit */
export const associate = {
'Complex,Complex': ({
'multiply(Complex,Complex)': times,
'equalTT(Complex,Complex)': eq,
zero,
one,
complex,
'negate(Complex)': neg
'Complex<T>,Complex<T>': ({
'multiply(Complex<T>,Complex<T>)': times,
'equalTT(Complex<T>,Complex<T>)': eq,
'zero(T)': zr,
'one(T)': uno,
'complex(T,T)': cplx,
'negate(Complex<T>)': neg
}) => (w,z) => {
if (eq(w,z) || eq(w,neg(z))) return true
const ti = times(z, complex(zero(z.re), one(z.im)))
const ti = times(z, cplx(zr(z.re), uno(z.im)))
return eq(w,ti) || eq(w,neg(ti))
}
}

View File

@ -12,5 +12,9 @@ export const complex = {
'undefined,undefined': () => (u, v) => u,
'T,T': () => (x, y) => ({re: x, im: y}),
/* Take advantage of conversions in typed-function */
Complex: () => z => z
// 'Complex<T>': () => z => z
/* But help out because without templates built in to typed-function,
* type inference turns out to be too hard
*/
'T': ({'zero(T)': zr}) => x => ({re: x, im: zr(x)})
}

View File

@ -1,6 +1,9 @@
export * from './Types/Complex.mjs'
export const conjugate = {
Complex: ({negate, complex}) => z => complex(z.re, negate(z.im))
'Complex<T>': ({
'negate(T)': neg,
'complex(T,T)': cplx
}) => z => cplx(z.re, neg(z.im))
}

View File

@ -1,19 +1,26 @@
export * from './Types/Complex.mjs'
export const equalTT = {
'Complex,number': ({
'isZero(number)': isZ,
'self(number,number)': eqNum
}) => (z, x) => eqNum(z.re, x) && isZ(z.im),
'Complex<T>,Complex<T>': ({
'self(T,T)': me
}) => (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<number>>, Complex<number>) (!, hopefully in some
// future iteration typed-function will be smart enough to prefer
// Complex<T>, Complex<T>. 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>,T': ({
'isZero(T)': isZ,
'self(T,T)': eqReal
}) => (z, x) => eqReal(z.re, x) && isZ(z.im),
'Complex,bigint': ({
'isZero(bigint)': isZ,
'self(bigint,bigint)': eqBigInt
}) => (z, b) => eqBigInt(z.re, b) && isZ(z.im),
'T,Complex<T>': ({
'isZero(T)': isZ,
'self(T,T)': eqReal
}) => (b, z) => eqReal(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)
}

View File

@ -15,4 +15,17 @@ 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)
}
}

View File

@ -2,16 +2,20 @@ import PocomathInstance from '../core/PocomathInstance.mjs'
import * as Complex from './Types/Complex.mjs'
import gcdType from '../generic/gcdType.mjs'
const gcdComplexRaw = {}
Object.assign(gcdComplexRaw, gcdType('Complex<bigint>'))
Object.assign(gcdComplexRaw, gcdType('Complex<NumInt>'))
const imps = {
gcdGIRaw: gcdType('GaussianInteger'),
gcdComplexRaw,
gcd: { // Only return gcds with positive real part
'GaussianInteger,GaussianInteger': ({
'gcdGIRaw(GaussianInteger,GaussianInteger)': gcdRaw,
'sign(bigint)': sgn,
'negate(GaussianInteger)': neg
'Complex<T>,Complex<T>': ({
'gcdComplexRaw(Complex<T>,Complex<T>)': gcdRaw,
'sign(T)': sgn,
'one(T)': uno,
'negate(Complex<T>)': neg
}) => (z,m) => {
const raw = gcdRaw(z, m)
if (sgn(raw.re) === 1n) return raw
if (sgn(raw.re) === uno(raw.re)) return raw
return neg(raw)
}
}

View File

@ -1,9 +1,14 @@
export * from './Types/Complex.mjs'
export const invert = {
Complex: ({conjugate, absquare, complex, divide}) => z => {
const c = conjugate(z)
const d = absquare(z)
return complex(divide(c.re, d), divide(c.im, d))
'Complex<T>': ({
'conjugate(Complex<T>)': conj,
'absquare(Complex<T>)': asq,
'complex(T,T)': cplx,
'divide(T,T)': div
}) => z => {
const c = conj(z)
const d = asq(z)
return cplx(div(c.re, d), div(c.im, d))
}
}

View File

@ -1,5 +1,5 @@
export * from './Types/Complex.mjs'
export const isZero = {
Complex: ({self}) => z => self(z.re) && self(z.im)
'Complex<T>': ({'self(T)': me}) => z => me(z.re) && me(z.im)
}

View File

@ -1,14 +1,15 @@
export * from './Types/Complex.mjs'
export const multiply = {
'Complex,Complex': ({
'complex(any,any)': cplx,
add,
subtract,
self
'Complex<T>,Complex<T>': ({
'complex(T,T)': cplx,
'add(T,T)': plus,
'subtract(T,T)': sub,
'self(T,T)': me,
'conjugate(T)': conj // makes quaternion multiplication work
}) => (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)))
sub(me(w.re, z.re), me(conj(w.im), z.im)),
plus(me(conj(w.re), z.im), me(w.im, z.re)))
}
}

View File

@ -1,5 +1,7 @@
export * from './roundquotient.mjs'
export const quotient = {
'Complex,Complex': ({roundquotient}) => (w,z) => roundquotient(w,z)
'Complex<T>,Complex<T>': ({
'roundquotient(Complex<T>,Complex<T>)': rq
}) => (w,z) => rq(w,z)
}

View File

@ -1,17 +1,17 @@
export * from './Types/Complex.mjs'
export const roundquotient = {
'Complex,Complex': ({
'isZero(Complex)': isZ,
conjugate,
'multiply(Complex,Complex)': mult,
absquare,
self,
complex
'Complex<T>,Complex<T>': ({
'isZero(Complex<T>)': isZ,
'conjugate(Complex<T>)': conj,
'multiply(Complex<T>,Complex<T>)': mult,
'absquare(Complex<T>)': asq,
'self(T,T)': me,
'complex(T,T)': cplx
}) => (n,d) => {
if (isZ(d)) return d
const cnum = mult(n, conjugate(d))
const dreal = absquare(d)
return complex(self(cnum.re, dreal), self(cnum.im, dreal))
const cnum = mult(n, conj(d))
const dreal = asq(d)
return cplx(me(cnum.re, dreal), me(cnum.im, dreal))
}
}

View File

@ -1,38 +1,39 @@
export * from './Types/Complex.mjs'
export const sqrt = {
Complex: ({
'Complex<T>': ({
config,
isZero,
sign,
one,
add,
complex,
multiply,
self,
divide,
'abs(Complex)': abs,
subtract
'isZero(T)': isZ,
'sign(T)': sgn,
'one(T)': uno,
'add(T,T)': plus,
'complex(T)': cplxU,
'complex(T,T)': cplxB,
'multiply(T,T)': mult,
'self(T)': me,
'divide(T,T)': div,
'abs(Complex<T>)': absC,
'subtract(T,T)': sub
}) => {
if (config.predictable) {
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))
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re))
const reTwo = plus(reOne, reOne)
return cplxB(
mult(sgn(z.im), me(div(plus(absC(z),z.re), reTwo))),
me(div(sub(absC(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))
)
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re)
const reTwo = plus(reOne, reOne)
const reSqrt = me(div(plus(absC(z),z.re), reTwo))
const imSqrt = me(div(sub(absC(z),z.re), reTwo))
if (reSqrt === undefined || imSqrt === undefined) return undefined
return cplxB(mult(sgn(z.im), reSqrt), imSqrt)
}
}
}

View File

@ -28,6 +28,7 @@ export default class PocomathInstance {
'importDependencies',
'install',
'installType',
'instantiateTemplate',
'joinTypes',
'name',
'self',
@ -436,7 +437,12 @@ export default class PocomathInstance {
}
return
}
// Nothing actually happens until we match a template parameter
// update the typeOf function
const imp = {}
imp[type] = {uses: new Set(['T']), does: ({T}) => () => `${base}<${T}>`}
this._installFunctions({typeOf: imp})
// Nothing else actually happens until we match a template parameter
this.Templates[base] = {type, spec}
}
@ -767,8 +773,9 @@ export default class PocomathInstance {
const subsig = substituteInSig(
needsig, theTemplateParam, instantiateFor)
let resname = simplifiedDep
if (resname === 'self') resname = name
innerRefs[dep] = self._pocoresolve(resname, subsig)
if (resname == 'self') resname = name
innerRefs[dep] = self._pocoresolve(
resname, subsig, refs[simplifiedDep])
} else {
innerRefs[dep] = refs[simplifiedDep]
}
@ -782,7 +789,7 @@ export default class PocomathInstance {
this._addTFimplementation(
tf_imps, signature, {uses: outerUses, does: patch})
}
this._correctPartialSelfRefs(tf_imps)
this._correctPartialSelfRefs(name, tf_imps)
const tf = this._typed(name, tf_imps)
Object.defineProperty(this, name, {configurable: true, value: tf})
return tf
@ -845,7 +852,7 @@ export default class PocomathInstance {
} else {
// can bundle up func, and grab its signature if need be
let destination = this[func]
if (needsig) {
if (destination &&needsig) {
destination = this._pocoresolve(func, needsig)
}
refs[dep] = destination
@ -878,7 +885,7 @@ export default class PocomathInstance {
imps[signature] = does(refs)
}
_correctPartialSelfRefs(imps) {
_correctPartialSelfRefs(name, imps) {
for (const aSignature in imps) {
if (!(imps[aSignature].deferred)) continue
const part_self_references = imps[aSignature].psr
@ -894,7 +901,7 @@ export default class PocomathInstance {
// 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)
const foundSig = this._findSubtypeImpl(name, imps, neededSig)
if (foundSig) {
corrected_self_references.push(foundSig)
} else {
@ -936,7 +943,7 @@ export default class PocomathInstance {
}
const resultingTypes = new Set()
for (const iType of instantiations) {
const resultType = this._maybeAddTemplateType(base, iType)
const resultType = this.instantiateTemplate(base, iType)
if (resultType) resultingTypes.add(resultType)
}
return resultingTypes
@ -946,7 +953,7 @@ export default class PocomathInstance {
* instantiator to the Types of this instance, if it hasn't happened already.
* Returns the name of the type if added, false otherwise.
*/
_maybeAddTemplateType(base, instantiator) {
instantiateTemplate(base, instantiator) {
const wantsType = `${base}<${instantiator}>`
if (wantsType in this.Types) return false
// OK, need to generate the type from the template
@ -956,7 +963,7 @@ export default class PocomathInstance {
const template = this.Templates[base].spec
if (!template) {
throw new Error(
`Implementor error in _maybeAddTemplateType ${base} ${instantiator}`)
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
}
const instantiatorSpec = this.Types[instantiator]
let beforeTypes = []
@ -1010,7 +1017,7 @@ export default class PocomathInstance {
return wantsType
}
_findSubtypeImpl(imps, neededSig) {
_findSubtypeImpl(name, imps, neededSig) {
if (neededSig in imps) return neededSig
let foundSig = false
const typeList = typeListOfSignature(neededSig)
@ -1047,6 +1054,13 @@ export default class PocomathInstance {
|| this._subtypes[otherType].has(myType)) {
continue
}
if (otherType in this.Templates) {
if (this.instantiateTemplate(otherType, myType)) {
let dummy
dummy = this[name] // for side effects
return this._findSubtypeImpl(name, this._imps[name], neededSig)
}
}
allMatch = false
break
}
@ -1058,17 +1072,28 @@ export default class PocomathInstance {
return foundSig
}
_pocoresolve(name, sig) {
const typedfunc = this[name]
_pocoresolve(name, sig, typedFunction) {
if (!this._typed.isTypedFunction(typedFunction)) {
typedFunction = this[name]
}
let result = undefined
try {
result = this._typed.find(typedfunc, sig, {exact: true})
result = this._typed.find(typedFunction, sig, {exact: true})
} catch {
}
if (result) return result
const foundsig = this._findSubtypeImpl(this._imps[name], sig)
if (foundsig) return this._typed.find(typedfunc, foundsig)
return this._typed.find(typedfunc, sig)
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
if (foundsig) return this._typed.find(typedFunction, foundsig)
// Make sure bundle is up-to-date:
typedFunction = this[name]
try {
result = this._typed.find(typedFunction, sig)
} catch {
}
if (result) return result
// total punt, revert to typed-function resolution on every call;
// hopefully this happens rarely:
return typedFunction
}
}

View File

@ -4,6 +4,7 @@ export const reducingOperation = {
'undefined': () => u => u,
'undefined,...any': () => (u, rest) => u,
'any,undefined': () => (x, u) => u,
'undefined,undefined': () => (u,v) => u,
any: () => x => x,
'any,any,...any': ({
self

View File

@ -1,14 +1,17 @@
export * from './Types/number.mjs'
export const sqrt = {
number: ({config, complex, 'self(Complex)': complexSqrt}) => {
if (config.predictable || !complexSqrt) {
number: ({
config,
'complex(number,number)': cplx,
'negate(number)': neg}) => {
if (config.predictable || !cplx) {
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))
return cplx(0, Math.sqrt(neg(n)))
}
}
}

View File

@ -7,7 +7,7 @@ import {Complex} from '../complex/Types/Complex.mjs'
export const floor = {
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
'Complex<bigint>': () => 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
@ -17,7 +17,7 @@ export const floor = {
return Math.floor(n)
},
Complex: Complex.promoteUnary.Complex,
'Complex<T>': Complex.promoteUnary['Complex<T>'],
// OK to include a type totally not in Pocomath yet, it'll never be
// activated.

View File

@ -32,7 +32,12 @@ Tuple.installType('Tuple<T>', {
})
Tuple.promoteUnary = {
'Tuple<T>': ({'self(T)': me, tuple}) => t => tuple(...(t.elts.map(me)))
'Tuple<T>': ({
'self(T)': me,
tuple
}) => t => tuple(...(t.elts.map(x => me(x)))) // NOTE: this must use
// the inner arrow function to drop additional arguments that Array.map
// supplies, as otherwise the wrong signature of `me` might be used.
}
Tuple.promoteBinaryUnary = {

View File

@ -16,8 +16,8 @@ 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}), 'GaussianInteger')
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex')
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex<bigint>')
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex<number>')
})
it('can subtract numbers', () => {
@ -105,11 +105,9 @@ describe('The default full pocomath instance "math"', () => {
it('calculates multi-way gcds and lcms', () => {
assert.strictEqual(math.gcd(30,105,42), 3)
assert.ok(
math.associate(
math.lcm(
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)),
math.complex(1n,3n)))
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)
})
})

View File

@ -49,6 +49,15 @@ describe('complex', () => {
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', () => {

View File

@ -3,12 +3,14 @@ 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'
@ -54,9 +56,9 @@ describe('A custom instance', () => {
math.complex(-5, -1))
// And now floor has been activated for Complex as well, since the type
// is present
assert.deepStrictEqual(
pm.floor(math.complex(1.9, 0)),
math.complex(1))
const fracComplex = math.complex(1.9, 0)
const intComplex = math.complex(1)
assert.deepStrictEqual(pm.floor(fracComplex), intComplex)
// And the chain functions refresh themselves:
assert.deepStrictEqual(
pm.chain(5).add(pm.chain(0).complex(7).value).value, math.complex(5,7))
@ -65,10 +67,11 @@ 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}) => (w,z) => {
return complex(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re)
'Complex<T>,Complex<T>': ({'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 type not present but should still be able to add numbers:
@ -82,6 +85,7 @@ 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
@ -124,12 +128,13 @@ 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 GaussianInteger')
'Merge to Complex<bigint>')
assert.strictEqual(
inst.typeMerge(3, inst.complex(4.5,2.1)),
'Merge to Complex')
'Merge to Complex<number>')
// The following is the current behavior, since 3 converts to 3+0i
// which is technically the same Complex type as 3n+0ni.
// This should clear up when Complex is templatized