feat: Add return types for all generic operations
This commit is contained in:
parent
23b3ef4fdd
commit
3957ae8adf
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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))))
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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)}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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
20
test/generic/_all.mjs
Normal 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')
|
||||
})
|
||||
})
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user