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 PocomathInstance from '../../core/PocomathInstance.mjs'
import Returns from '../../core/Returns.mjs'
/* creates a PocomathInstance incorporating a new numeric type encapsulated /* creates a PocomathInstance incorporating a new numeric type encapsulated
* as a class. (This instance can the be `install()ed` in another to add the * as a class. (This instance can the be `install()ed` in another to add the
* type so created.) * 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: // first a creator function, with name depending on the name of the thing:
const creatorName = overrides.creatorName || name.toLowerCase() const creatorName = overrides.creatorName || name.toLowerCase()
const creator = overrides[creatorName] const creator = overrides[creatorName]
? overrides[creatorName]('') ? overrides[creatorName]['']
: Thing[creatorName] : Thing[creatorName]
? (Thing[creatorName]) ? (Thing[creatorName])
: ((...args) => new Thing(...args)) : ((...args) => new Thing(...args))
const defaultCreatorImps = { const defaultCreatorImps = {
'': () => () => creator(), '': () => Returns(name, () => creator()),
'...any': () => args => creator(...args) '...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 operations[creatorName] = overrides[creatorName] || defaultCreatorImps
// We make the default instance, just as a place to check for methods // 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' negate: 'neg'
} }
const binaryOps = { const binaryOps = {
add: 'add', add: ['add', name],
compare: 'compare', compare: ['compare', name],
divide: 'div', divide: ['div', name],
equalTT: 'equals', equalTT: ['equals', 'boolean'],
gcd: 'gcd', gcd: ['gcd', name],
lcm: 'lcm', lcm: ['lcm', name],
mod: 'mod', mod: ['mod', name],
multiply: 'mul', multiply: ['mul', name],
subtract: 'sub' subtract: ['sub', name]
} }
for (const [mathname, standardname] of Object.entries(unaryOps)) { for (const [mathname, standardname] of Object.entries(unaryOps)) {
if (standardname in instance) { if (standardname in instance) {
operations[mathname] = {} operations[mathname] = {}
operations[mathname][name] = () => t => t[standardname]() operations[mathname][name] = () => Returns(name, t => t[standardname]())
} }
} }
operations.zero = {} operations.zero = {}
operations.zero[name] = () => t => creator() operations.zero[name] = () => Returns(name, t => creator())
operations.one = {} operations.one = {}
operations.one[name] = () => t => creator(1) operations.one[name] = () => Returns(name, t => creator(1))
operations.conjugate = {} operations.conjugate = {}
operations.conjugate[name] = () => t => t // or t.clone() ?? operations.conjugate[name] = () => Returns(name, t => t) // or t.clone() ??
const binarySignature = `${name},${name}` const binarySignature = `${name},${name}`
for (const [mathname, standardname] of Object.entries(binaryOps)) { for (const [mathname, spec] of Object.entries(binaryOps)) {
if (standardname in instance) { if (spec[0] in instance) {
operations[mathname] = {} 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) { if ('operations' in overrides) {

View File

@ -1,5 +1,6 @@
import {adapted} from './Types/adapted.mjs' import {adapted} from './Types/adapted.mjs'
import Fraction from 'fraction.js/bigfraction.js' import Fraction from 'fraction.js/bigfraction.js'
import Returns from '../core/Returns.mjs'
export * from './arithmetic.mjs' export * from './arithmetic.mjs'
export * from './relational.mjs' export * from './relational.mjs'
@ -8,15 +9,18 @@ export const fraction = adapted('Fraction', Fraction, {
before: ['Complex'], before: ['Complex'],
from: {number: n => new Fraction(n)}, from: {number: n => new Fraction(n)},
operations: { operations: {
compare: {'Fraction,Fraction': () => (f,g) => new Fraction(f.compare(g))}, compare: {
'Fraction,Fraction': () => Returns(
'Fraction', (f,g) => new Fraction(f.compare(g)))
},
mod: { mod: {
'Fraction,Fraction': () => (n,d) => { 'Fraction,Fraction': () => Returns('Fraction', (n,d) => {
// patch for "mathematician's modulus" // patch for "mathematician's modulus"
// OK to use full public API of Fraction here // OK to use full public API of Fraction here
const fmod = n.mod(d) const fmod = n.mod(d)
if (fmod.s === -1n) return fmod.add(d.abs()) if (fmod.s === -1n) return fmod.add(d.abs())
return fmod return fmod
} })
} }
} }
}) })

View File

@ -1,7 +1,10 @@
import Returns from '../core/Returns.mjs'
export const divide = { export const divide = {
'T,T': ({ 'T,T': ({
T,
'multiply(T,T)': multT, 'multiply(T,T)': multT,
'invert(T)': invT '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' import {reducingOperation} from './reducingOperation.mjs'
export const lcm = { export const lcm = {
'T,T': ({ 'T,T': ({
T,
'multiply(T,T)': multT, 'multiply(T,T)': multT,
'quotient(T,T)': quotT, 'quotient(T,T)': quotT,
'gcd(T,T)': gcdT '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) Object.assign(lcm, reducingOperation)

View File

@ -1,3 +1,8 @@
import Returns from '../core/Returns.mjs'
export const mean = { 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 = { export const mod = {
'T,T': ({ 'T,T': ({
T,
'subtract(T,T)': subT, 'subtract(T,T)': subT,
'multiply(T,T)': multT, 'multiply(T,T)': multT,
'quotient(T,T)': quotT '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 = { 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 * from './Types/generic.mjs'
export const reducingOperation = { export const reducingOperation = {
'undefined': () => u => u, 'undefined': () => Returns('undefined', u => u),
'undefined,...any': () => (u, rest) => u, 'undefined,...any': () => Returns('undefined', (u, rest) => u),
'any,undefined': () => (x, u) => u, 'any,undefined': () => Returns('undefined', (x, u) => u),
'undefined,undefined': () => (u,v) => u, 'undefined,undefined': () => Returns('undefined', (u,v) => u),
any: () => x => x, 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': ({ 'any,any,...any': ({
self 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 = { export const compare = {
'undefined,undefined': () => () => 0 'undefined,undefined': () => Returns('NumInt', () => 0)
} }
export const isZero = { export const isZero = {
'undefined': () => u => u === 0, 'undefined': () => Returns('boolean', u => u === 0),
T: ({'equal(T,T)': eq, 'zero(T)': zr}) => t => eq(t, zr(t)) T: ({
T,
'equal(T,T)': eq,
'zero(T)': zr
}) => Returns('boolean', t => eq(t, zr(t)))
} }
export const equal = { 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') const resultant = joinTypes([typeOf(x), typeOf(y)], 'convert')
if (resultant === 'any' || resultant in Templates) { if (resultant === 'any' || resultant in Templates) {
return false return false
} }
return equalTT(x,y) return equalTT(x,y)
} })
} }
export const equalTT = { export const equalTT = {
'T,T': ({ 'T,T': ({
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'isZero(T)': isZ '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 // If templates were native to typed-function, we should be able to
// do something like: // do something like:
// 'any,any': () => () => false // should only be hit for different types // 'any,any': () => () => false // should only be hit for different types
} }
export const unequal = { export const unequal = {
'any,any': ({equal}) => (x,y) => !(equal(x,y)) 'any,any': ({equal}) => Returns('boolean', (x,y) => !(equal(x,y)))
} }
export const larger = { export const larger = {
@ -36,7 +47,7 @@ export const larger = {
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'one(T)' : uno, 'one(T)' : uno,
'equalTT(T,T)' : eq '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 = { export const largerEq = {
@ -45,10 +56,10 @@ export const largerEq = {
'one(T)' : uno, 'one(T)' : uno,
'isZero(T)' : isZ, 'isZero(T)' : isZ,
'equalTT(T,T)': eq 'equalTT(T,T)': eq
}) => (x,y) => { }) => Returns('boolean', (x,y) => {
const c = cmp(x,y) const c = cmp(x,y)
return isZ(c) || eq(c, uno(y)) return isZ(c) || eq(c, uno(y))
} })
} }
export const smaller = { export const smaller = {
@ -57,10 +68,10 @@ export const smaller = {
'one(T)' : uno, 'one(T)' : uno,
'isZero(T)' : isZ, 'isZero(T)' : isZ,
unequal unequal
}) => (x,y) => { }) => Returns('boolean', (x,y) => {
const c = cmp(x,y) const c = cmp(x,y)
return !isZ(c) && unequal(c, uno(y)) return !isZ(c) && unequal(c, uno(y))
} })
} }
export const smallerEq = { export const smallerEq = {
@ -68,5 +79,5 @@ export const smallerEq = {
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'one(T)' : uno, 'one(T)' : uno,
unequal 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 = { 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 = { 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 * 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 = { 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)) math.add(3, math.complex(2.5, 1)), math.complex(5.5, 1))
assert.strictEqual( assert.strictEqual(
math.returnTypeOf('add', 'Complex<number>,NumInt'), 'Complex<number>') 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( assert.strictEqual(
math.returnTypeOf('chain', 'bigint'), 'Chain<bigint>') math.returnTypeOf('chain', 'bigint'), 'Chain<bigint>')
assert.strictEqual( 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)) 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')
})
}) })