feat: Add return types for all generic operations

This commit is contained in:
Glen Whitney 2022-08-29 19:42:48 -04:00
parent 23b3ef4fdd
commit 3957ae8adf
16 changed files with 141 additions and 51 deletions

View File

@ -1,4 +1,6 @@
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.)
@ -22,15 +24,15 @@ export default function adapted(name, Thing, overrides) {
// 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]('')
? overrides[creatorName]['']
: Thing[creatorName]
? (Thing[creatorName])
: ((...args) => new Thing(...args))
const defaultCreatorImps = {
'': () => () => creator(),
'...any': () => args => creator(...args)
'': () => Returns(name, () => creator()),
'...any': () => Returns(name, args => creator(...args))
}
defaultCreatorImps[name] = () => x => x // x.clone(x)?
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
@ -47,34 +49,35 @@ export default function adapted(name, Thing, overrides) {
negate: 'neg'
}
const binaryOps = {
add: 'add',
compare: 'compare',
divide: 'div',
equalTT: 'equals',
gcd: 'gcd',
lcm: 'lcm',
mod: 'mod',
multiply: 'mul',
subtract: 'sub'
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] = () => t => t[standardname]()
operations[mathname][name] = () => Returns(name, t => t[standardname]())
}
}
operations.zero = {}
operations.zero[name] = () => t => creator()
operations.zero[name] = () => Returns(name, t => creator())
operations.one = {}
operations.one[name] = () => t => creator(1)
operations.one[name] = () => Returns(name, t => creator(1))
operations.conjugate = {}
operations.conjugate[name] = () => t => t // or t.clone() ??
operations.conjugate[name] = () => Returns(name, t => t) // or t.clone() ??
const binarySignature = `${name},${name}`
for (const [mathname, standardname] of Object.entries(binaryOps)) {
if (standardname in instance) {
for (const [mathname, spec] of Object.entries(binaryOps)) {
if (spec[0] in instance) {
operations[mathname] = {}
operations[mathname][binarySignature] = () => (t,u) => t[standardname](u)
operations[mathname][binarySignature] = () => Returns(
spec[1], (t,u) => t[spec[0]](u))
}
}
if ('operations' in overrides) {

View File

@ -1,5 +1,6 @@
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'
@ -8,15 +9,18 @@ export const fraction = adapted('Fraction', Fraction, {
before: ['Complex'],
from: {number: n => new Fraction(n)},
operations: {
compare: {'Fraction,Fraction': () => (f,g) => new Fraction(f.compare(g))},
compare: {
'Fraction,Fraction': () => Returns(
'Fraction', (f,g) => new Fraction(f.compare(g)))
},
mod: {
'Fraction,Fraction': () => (n,d) => {
'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
}
})
}
}
})

View File

@ -1,7 +1,10 @@
import Returns from '../core/Returns.mjs'
export const divide = {
'T,T': ({
T,
'multiply(T,T)': multT,
'invert(T)': invT
}) => (x, y) => multT(x, invT(y))
}) => Returns(T, (x, y) => multT(x, invT(y)))
}

View File

@ -1,10 +1,12 @@
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
}) => (a,b) => multT(quotT(a, gcdT(a,b)), b)
}) => Returns(T, (a,b) => multT(quotT(a, gcdT(a,b)), b))
}
Object.assign(lcm, reducingOperation)

View File

@ -1,3 +1,8 @@
import Returns from '../core/Returns.mjs'
export const mean = {
'...any': ({add, divide}) => args => divide(add(...args), args.length)
'...T': ({
T,
add,
'divide(T,NumInt)': div
}) => Returns(T, args => div(add(...args), args.length))
}

View File

@ -1,7 +1,10 @@
import Returns from '../core/Returns.mjs'
export const mod = {
'T,T': ({
T,
'subtract(T,T)': subT,
'multiply(T,T)': multT,
'quotient(T,T)': quotT
}) => (a,m) => subT(a, multT(m, quotT(a,m)))
}) => Returns(T, (a,m) => subT(a, multT(m, quotT(a,m))))
}

View File

@ -1,3 +1,9 @@
import Returns from '../core/Returns.mjs'
export const quotient = {
'T,T': ({'floor(T)': flr, 'divide(T,T)':div}) => (n,d) => flr(div(n,d))
'T,T': ({
T,
'floor(T)': flr,
'divide(T,T)': div
}) => Returns(T, (n,d) => flr(div(n,d)))
}

View File

@ -1,13 +1,16 @@
import Returns from '../core/Returns.mjs'
export * from './Types/generic.mjs'
export const reducingOperation = {
'undefined': () => u => u,
'undefined,...any': () => (u, rest) => u,
'any,undefined': () => (x, u) => u,
'undefined,undefined': () => (u,v) => u,
any: () => x => x,
'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:
'any,any,...any': ({
self
}) => (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a)
}) => Returns('any', (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a))
}

View File

@ -1,34 +1,45 @@
import Returns from '../core/Returns.mjs'
export const compare = {
'undefined,undefined': () => () => 0
'undefined,undefined': () => Returns('NumInt', () => 0)
}
export const isZero = {
'undefined': () => u => u === 0,
T: ({'equal(T,T)': eq, 'zero(T)': zr}) => t => eq(t, zr(t))
'undefined': () => Returns('boolean', u => u === 0),
T: ({
T,
'equal(T,T)': eq,
'zero(T)': zr
}) => Returns('boolean', t => eq(t, zr(t)))
}
export const equal = {
'any,any': ({equalTT, joinTypes, Templates, typeOf}) => (x,y) => {
'any,any': ({
equalTT,
joinTypes,
Templates,
typeOf
}) => Returns('boolean', (x,y) => {
const resultant = joinTypes([typeOf(x), typeOf(y)], 'convert')
if (resultant === 'any' || resultant in Templates) {
return false
}
return equalTT(x,y)
}
})
}
export const equalTT = {
'T,T': ({
'compare(T,T)': cmp,
'isZero(T)': isZ
}) => (x,y) => isZ(cmp(x,y)),
}) => Returns('boolean', (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}) => (x,y) => !(equal(x,y))
'any,any': ({equal}) => Returns('boolean', (x,y) => !(equal(x,y)))
}
export const larger = {
@ -36,7 +47,7 @@ export const larger = {
'compare(T,T)': cmp,
'one(T)' : uno,
'equalTT(T,T)' : eq
}) => (x,y) => eq(cmp(x,y), uno(y))
}) => Returns('boolean', (x,y) => eq(cmp(x,y), uno(y)))
}
export const largerEq = {
@ -45,10 +56,10 @@ export const largerEq = {
'one(T)' : uno,
'isZero(T)' : isZ,
'equalTT(T,T)': eq
}) => (x,y) => {
}) => Returns('boolean', (x,y) => {
const c = cmp(x,y)
return isZ(c) || eq(c, uno(y))
}
})
}
export const smaller = {
@ -57,10 +68,10 @@ export const smaller = {
'one(T)' : uno,
'isZero(T)' : isZ,
unequal
}) => (x,y) => {
}) => Returns('boolean', (x,y) => {
const c = cmp(x,y)
return !isZ(c) && unequal(c, uno(y))
}
})
}
export const smallerEq = {
@ -68,5 +79,5 @@ export const smallerEq = {
'compare(T,T)': cmp,
'one(T)' : uno,
unequal
}) => (x,y) => unequal(cmp(x,y), uno(y))
}) => Returns('boolean', (x,y) => unequal(cmp(x,y), uno(y)))
}

View File

@ -1,3 +1,9 @@
import Returns from '../core/Returns.mjs'
export const roundquotient = {
'T,T': ({'round(T)': rnd, 'divide(T,T)':div}) => (n,d) => rnd(div(n,d))
'T,T': ({
T,
'round(T)': rnd,
'divide(T,T)':div
}) => Returns(T, (n,d) => rnd(div(n,d)))
}

View File

@ -1,3 +1,9 @@
import Returns from '../core/Returns.mjs'
export const sign = {
T: ({'compare(T,T)': cmp, 'zero(T)': Z}) => x => cmp(x, Z(x))
T: ({
T,
'compare(T,T)': cmp,
'zero(T)': Z
}) => Returns(T, x => cmp(x, Z(x)))
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/generic.mjs'
export const sqrt = {undefined: () => () => undefined}
export const sqrt = {undefined: () => Returns('undefined', () => undefined)}

View File

@ -1,3 +1,9 @@
import Returns from '../core/Returns.mjs'
export const subtract = {
'T,T': ({'add(T,T)': addT, 'negate(T)': negT}) => (x,y) => addT(x, negT(y))
'T,T': ({
T,
'add(T,T)': addT,
'negate(T)': negT
}) => Returns(T, (x,y) => addT(x, negT(y)))
}

View File

@ -34,6 +34,11 @@ describe('The default full pocomath instance "math"', () => {
math.add(3, math.complex(2.5, 1)), math.complex(5.5, 1))
assert.strictEqual(
math.returnTypeOf('add', 'Complex<number>,NumInt'), 'Complex<number>')
// 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<number>'), 'any')
assert.strictEqual(
math.returnTypeOf('chain', 'bigint'), 'Chain<bigint>')
assert.strictEqual(

20
test/generic/_all.mjs Normal file
View File

@ -0,0 +1,20 @@
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')
})
})

View File

@ -92,4 +92,10 @@ describe('fraction', () => {
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')
})
})