Compare commits

...

15 Commits

Author SHA1 Message Date
Glen Whitney d83f2a7f23 feat: Instantiate instances of template instantiation need for self-reference
Formerly, when a self-reference like `self(Tuple<number>)` was encountered,
  but the `Tuple<number>` instance of a `Tuple<T>` implementation for this
  operation had not yet been instantiated, the reference would be fulfilled
  with a call to the catchall implementation for `Tuple`. Now the
  necessary instance is instantiated on the spot and referred to instead.

  This change is used to complete return-type specification for all of the
  Tuple functions.
2022-08-30 15:15:23 -04:00
Glen Whitney be9794fd4c feat(ops): Provide return types in all of the operation-centric examples 2022-08-29 21:30:32 -04:00
Glen Whitney 1ee6da4294 feat(number): Provide return types for all operations 2022-08-29 21:18:59 -04:00
Glen Whitney 3957ae8adf feat: Add return types for all generic operations 2022-08-29 19:42:48 -04:00
Glen Whitney 23b3ef4fdd feat(Complex): Define return types of all operations 2022-08-29 11:10:34 -04:00
Glen Whitney de42c22ab4 refactor: Convert resolution to two-tier system
Typed-function's sort order/matching algorithm was interfering with
  template resolution. This commit solves the difficulty by moving the
  "catchall" implementations that implement generation of new template
  instances into a separate "fallback" typed-function universe, so that
  Pocomath can control exactly when that is searched.

  Removes a couple of the matching anomalies already noted in the tests.

  Also extends return types to somewhat more functions.
2022-08-29 09:30:07 -04:00
Glen Whitney a2f76a55b8 feat(return types): Add more return types for complex functions
These changes greatly increased the need for precision in generating
  implementations for signatures of operations whenever possible. So this
  commit also includes a refactor that basically caches all of the
  conversions of Pocomath implementations to typed-function implementatios so
  that they are more often externally available (i.e., not disrupted so much
  after invalidation).
2022-08-26 23:54:32 -04:00
Glen Whitney bc434c7163 feat(bigint): Specify return types for all methods 2022-08-24 20:59:30 -04:00
Glen Whitney 0950d7d585 feat(PocomathInstance): Specify return types of all core methods 2022-08-24 19:34:33 -04:00
Glen Whitney 775bb9ddb7 feat(returnTypeOf): Support template and upper bound types
Also changed the notation for an upper bound template to the more
  readable 'T:number' (instead of 'T (= number', which was supposed to
  look like the non-strict subset relation).
2022-08-24 13:37:32 -04:00
Glen Whitney f7bb3697ed feat(PocomathInstance): Add bounded template parameters
This feature helps specify the return type of implementations where
  the return type depends on the exact subtype that the implementation
  was called with, such as negate.
2022-08-22 12:38:23 -04:00
Glen Whitney dc6921e768 feat(PocomathInstance): Add subtypesOf and isSubtypeOf methods
Note this involves refeactoring the internal subtype tracker to keep
  subtypes in a list sorted in topological order.
2022-08-12 09:10:53 -07:00
Glen Whitney 340dbd436e feat(PocomathInstance): Add .returnTypeOf method 2022-08-11 23:51:47 -07:00
Glen Whitney 95f6ccc5a0 refactor: Put return types as properties on implementations 2022-08-11 08:13:10 -07:00
Glen Whitney 4b28f8eac0 refactor: Add a stub for supplying return type information
With this commit, there's a rudimentary function that attaches return-type
  information, so it can be supplied, and PocomathInstance then proceeds
  to check for it everywhere necessary (and supply it in all the internal
  operations it builds). But so far in all of those place where it's checked
  for, it's just ignored.

  I think the next step will be just to put the return type info as a property
  on the function itself. That should actually ease all of the code.
2022-08-10 12:00:06 -07:00
80 changed files with 1502 additions and 606 deletions

View File

@ -1,6 +1,7 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
/* Absolute value squared */
export const absquare = {
bigint: ({'square(bigint)': sqb}) => b => sqb(b)
bigint: ({'square(bigint)': sqb}) => Returns('bigint', b => sqb(b))
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const add = {'bigint,bigint': () => (a,b) => a+b}
export const add = {'bigint,bigint': () => Returns('bigint', (a,b) => a+b)}

View File

@ -1,5 +1,7 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const compare = {
'bigint,bigint': () => (a,b) => a === b ? 0n : (a > b ? 1n : -1n)
'bigint,bigint': () => Returns(
'boolean', (a,b) => a === b ? 0n : (a > b ? 1n : -1n))
}

View File

@ -1,12 +1,13 @@
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 quot
return (n, d) => {
if (config.predictable) return Returns('bigint', (n,d) => quot(n,d))
return Returns('bigint|undefined', (n, d) => {
const q = n/d
if (q * d == n) return q
return undefined
}
})
}
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const isZero = {bigint: () => b => b === 0n}
export const isZero = {bigint: () => Returns('boolean', b => b === 0n)}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const multiply = {'bigint,bigint': () => (a,b) => a*b}
export const multiply = {'bigint,bigint': () => Returns('bigint', (a,b) => a*b)}

View File

@ -1,12 +1,12 @@
import gcdType from '../generic/gcdType.mjs'
import {identity} from '../generic/identity.mjs'
import {identityType} 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: () => identity}
export const conjugate = {bigint: identityType('bigint')}
export {divide} from './divide.mjs'
export const gcd = gcdType('bigint')
export {isZero} from './isZero.mjs'

View File

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

View File

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

View File

@ -1,13 +1,14 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
/* Returns the best integer approximation to n/d */
/* Returns the floor integer approximation to n/d */
export const quotient = {
'bigint,bigint': ({'sign(bigint)': sgn}) => (n, d) => {
'bigint,bigint': ({'sign(bigint)': sgn}) => Returns('bigint', (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
}
})
}

View File

@ -1,8 +1,9 @@
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}) => (n, d) => {
'bigint,bigint': ({'sign(bigint)': sgn}) => Returns('bigint', (n, d) => {
const dSgn = sgn(d)
if (dSgn === 0n) return 0n
const candidate = n/d
@ -11,5 +12,5 @@ export const roundquotient = {
if (2n * rem > absd) return candidate + dSgn
if (-2n * rem >= absd) return candidate - dSgn
return candidate
}
})
}

View File

@ -1,9 +1,10 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const sign = {
bigint: () => b => {
bigint: () => Returns('bigint', b => {
if (b === 0n) return 0n
if (b > 0n) return 1n
return -1n
}
})
}

View File

@ -1,5 +1,6 @@
export * from './Types/bigint.mjs'
import Returns from '../core/Returns.mjs'
import isqrt from 'bigint-isqrt'
export * from './Types/bigint.mjs'
export const sqrt = {
bigint: ({
@ -11,18 +12,18 @@ export const sqrt = {
// 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)
return Returns('bigint', b => isqrt(b))
}
if (!cplx) {
return b => {
return Returns('bigint|undefined', b => {
if (b >= 0n) {
const trial = isqrt(b)
if (trial * trial === b) return trial
}
return undefined
}
})
}
return b => {
return Returns('bigint|Complex<bigint>|undefined', b => {
if (b === undefined) return undefined
let real = true
if (b < 0n) {
@ -33,6 +34,6 @@ export const sqrt = {
if (trial * trial !== b) return undefined
if (real) return trial
return cplx(0n, trial)
}
})
}
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const zero = {bigint: () => () => 0n}
export const zero = {bigint: () => Returns('bigint', () => 0n)}

View File

@ -1,15 +1,14 @@
import {Returns, returnTypeOf} from '../../core/Returns.mjs'
import PocomathInstance from '../../core/PocomathInstance.mjs'
const Complex = new PocomathInstance('Complex')
// Base type that should generally not be used directly
Complex.installType('Complex', {
test: z => z && typeof z === 'object' && 're' in z && 'im' in z
})
// Now the template type: Complex numbers are actually always homeogeneous
// in their component types.
// 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<T>', {
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
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)]),
from: {
T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T)
U: convert => u => {
@ -21,7 +20,12 @@ Complex.installType('Complex<T>', {
})
Complex.promoteUnary = {
'Complex<T>': ({'self(T)': me, complex}) => z => complex(me(z.re), me(z.im))
'Complex<T>': ({
T,
'self(T)': me,
complex
}) => Returns(
`Complex<${returnTypeOf(me)}>`, z => complex(me(z.re), me(z.im)))
}
export {Complex}

View File

@ -1,10 +1,21 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const abs = {
'Complex<T>': ({
sqrt, // Calculation of the type needed in the square root (the
// underlying numeric type of T, whatever T is, is beyond Pocomath's
// (current) template abilities, so punt and just do full resolution
T,
sqrt, // Unfortunately no notation yet for the needed signature
'absquare(T)': baseabsq,
'absquare(Complex<T>)': absq
}) => z => sqrt(absq(z))
}) => {
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)))
}
}

View File

@ -1,9 +1,27 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const absquare = {
'Complex<T>': ({
add, // Calculation of exact type needed in add (underlying numeric of T)
// is (currently) too involved for Pocomath
add, // no easy way to write the needed signature; if T is number
// it is number,number; but if T is Complex<bigint>, it is just
// bigint,bigint. So unfortunately we depend on all of add, and
// we extract the needed implementation below.
'self(T)': absq
}) => z => add(absq(z.re), absq(z.im))
}) => {
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)))
}
}
/* We could imagine notations that Pocomath could support that would simplify
* the above, maybe something like
* 'Complex<T>': ({
* 'self(T): U': absq,
* 'add(U,U):V': plus,
* V
* }) => Returns(V, z => plus(absq(z.re), absq(z.im)))
*/

View File

@ -1,8 +1,10 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const add = {
'Complex<T>,Complex<T>': ({
T,
'self(T,T)': me,
'complex(T,T)': cplx
}) => (w,z) => cplx(me(w.re, z.re), me(w.im, z.im))
}) => Returns(`Complex<${T}>`, (w,z) => cplx(me(w.re, z.re), me(w.im, z.im)))
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
/* Returns true if w is z multiplied by a complex unit */
@ -9,9 +10,9 @@ export const associate = {
'one(T)': uno,
'complex(T,T)': cplx,
'negate(Complex<T>)': neg
}) => (w,z) => {
}) => Returns('boolean', (w,z) => {
if (eq(w,z) || eq(w,neg(z))) return true
const ti = times(z, cplx(zr(z.re), uno(z.im)))
return eq(w,ti) || eq(w,neg(ti))
}
})
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export * from '../generic/Types/generic.mjs'
@ -6,15 +7,16 @@ export const complex = {
* have a numeric/scalar type, e.g. by implementing subtypes in
* typed-function
*/
'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}),
'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})),
/* Take advantage of conversions in typed-function */
// '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)})
'T': ({T, 'zero(T)': zr}) => Returns(
`Complex<${T}>`, x => ({re: x, im: zr(x)}))
}

View File

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

View File

@ -1,9 +1,11 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const equalTT = {
'Complex<T>,Complex<T>': ({
T,
'self(T,T)': me
}) => (w,z) => me(w.re, z.re) && me(w.im, z.im),
}) => 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
@ -11,16 +13,16 @@ export const equalTT = {
// 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.
// 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),
}) => Returns('boolean', (z, x) => eqReal(z.re, x) && isZ(z.im)),
'T,Complex<T>': ({
'isZero(T)': isZ,
'self(T,T)': eqReal
}) => (b, z) => eqReal(z.re, b) && isZ(z.im),
}) => Returns('boolean', (b, z) => eqReal(z.re, b) && isZ(z.im)),
}

View File

@ -1,5 +1,6 @@
import PocomathInstance from '../core/PocomathInstance.mjs'
import * as Complex from './Types/Complex.mjs'
import Returns from '../core/Returns.mjs'
import * as Complex from './Types/Complex.mjs'
import gcdType from '../generic/gcdType.mjs'
const gcdComplexRaw = {}
@ -9,15 +10,16 @@ const imps = {
gcdComplexRaw,
gcd: { // Only return gcds with positive real part
'Complex<T>,Complex<T>': ({
T,
'gcdComplexRaw(Complex<T>,Complex<T>)': gcdRaw,
'sign(T)': sgn,
'one(T)': uno,
'negate(Complex<T>)': neg
}) => (z,m) => {
}) => Returns(`Complex<${T}>`, (z,m) => {
const raw = gcdRaw(z, m)
if (sgn(raw.re) === uno(raw.re)) return raw
return neg(raw)
}
})
}
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,14 @@
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': ({complex}) => (r,i,j,k) => complex(complex(r,j), complex(i,k))
'T,T,T,T': ({
T,
'complex(T,T)': cplxT,
'complex(Complex<T>,Complex<T>)': quat
}) => Returns(
`Complex<Complex<${T}>>`,
(r,i,j,k) => quat(cplxT(r,j), cplxT(i,k))
)
}

View File

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

View File

@ -1,17 +1,19 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const roundquotient = {
'Complex<T>,Complex<T>': ({
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) => {
}) => Returns(`Complex<${T}>`, (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))
}
})
}

View File

@ -1,3 +1,4 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const sqrt = {
@ -12,29 +13,41 @@ export const sqrt = {
'multiply(T,T)': mult,
'self(T)': me,
'divide(T,T)': div,
'abs(Complex<T>)': absC,
'absquare(Complex<T>)': absqC,
'subtract(T,T)': sub
}) => {
let baseReturns = returnTypeOf(me)
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]
}
if (config.predictable) {
return z => {
return Returns(`Complex<${baseReturns}>`, z => {
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re))
const reTwo = plus(reOne, reOne)
const myabs = me(absqC(z))
return cplxB(
mult(sgn(z.im), me(div(plus(absC(z),z.re), reTwo))),
me(div(sub(absC(z),z.re), reTwo))
mult(sgn(z.im), me(div(plus(myabs, z.re), reTwo))),
me(div(sub(myabs, z.re), reTwo))
)
})
}
return Returns(
`Complex<${baseReturns}>|${baseReturns}|undefined`,
z => {
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re)
const reTwo = plus(reOne, reOne)
const myabs = me(absqC(z))
const reSqrt = me(div(plus(myabs, z.re), reTwo))
const imSqrt = me(div(sub(myabs, z.re), reTwo))
if (reSqrt === undefined || imSqrt === undefined) return undefined
return cplxB(mult(sgn(z.im), reSqrt), imSqrt)
}
}
return z => {
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)
}
)
}
}

File diff suppressed because it is too large Load Diff

34
src/core/Returns.mjs Normal file
View File

@ -0,0 +1,34 @@
/* 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

View File

@ -8,6 +8,8 @@ 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())
}

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,7 +1,9 @@
import Returns from '../core/Returns.mjs'
export const abs = {
T: ({
T,
'smaller(T,T)': lt,
'negate(T)': neg,
'zero(T)': zr
}) => t => (smaller(t, zr(t)) ? neg(t) : t)
}) => Returns(T, t => (smaller(t, zr(t)) ? neg(t) : t))
}

View File

@ -1,6 +1,9 @@
import Returns from '../core/Returns.mjs'
export const absquare = {
T: ({
T,
'square(T)': sq,
'abs(T)': abval
}) => t => sq(abval(t))
}) => Returns(T, t => sq(abval(t)))
}

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,3 +1,5 @@
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.
@ -7,14 +9,14 @@ export default function(type) {
const producer = refs => {
const modder = refs[`mod(${type},${type})`]
const zeroTester = refs[`isZero(${type})`]
return (a,b) => {
return Returns(type, (a,b) => {
while (!zeroTester(b)) {
const r = modder(a,b)
a = b
b = r
}
return a
}
})
}
const retval = {}
retval[`${type},${type}`] = producer

View File

@ -1,3 +1,11 @@
export function identity(x) {
return x
import Returns from '../core/Returns.mjs'
export function identityType(type) {
return () => Returns(type, x => x)
}
export function identitySubTypes(type) {
return ({T}) => Returns(T, x => x)
}
export const identity = {T: ({T}) => Returns(T, x => x)}

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,6 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export const square = {
T: ({'multiply(T,T)': multT}) => x => multT(x,x)
T: ({'multiply(T,T)': multT}) => Returns(
returnTypeOf(multT), x => multT(x,x))
}

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

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const abs = {number: () => n => Math.abs(n)}
export const abs = {'T:number': ({T}) => Returns(T, n => Math.abs(n))}

View File

@ -1,6 +1,7 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
/* Absolute value squared */
export const absquare = {
number: ({'square(number)': sqn}) => n => sqn(n)
'T:number': ({T, 'square(T)': sqn}) => Returns(T, n => sqn(n))
}

View File

@ -1,3 +1,8 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const add = {'number,number': () => (m,n) => m+n}
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)
}

View File

@ -1,3 +1,5 @@
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
@ -48,5 +50,6 @@ function nearlyEqual (x, y, epsilon) {
export const compare = {
'number,number': ({
config
}) => (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1)
}) => Returns(
'NumInt', (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1))
}

View File

@ -1,3 +1,5 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const invert = {number: () => n => 1/n}
export const invert = {number: () => Returns('number', n => 1/n)}

View File

@ -1,6 +1,6 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const isZero = {
number: () => n => n === 0,
NumInt: () => n => n === 0 // necessary because of generic template
'T:number': () => Returns('boolean', n => n === 0)
}

View File

@ -1,3 +1,5 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const multiply = {'number,number': () => (m,n) => m*n}
export const multiply = {'T:number,T': ({T}) => Returns(T, (m,n) => m*n)}

View File

@ -1,5 +1,5 @@
import gcdType from '../generic/gcdType.mjs'
import {identity} from '../generic/identity.mjs'
import {identitySubTypes} from '../generic/identity.mjs'
export * from './Types/number.mjs'
@ -7,7 +7,7 @@ export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs'
export {add} from './add.mjs'
export {compare} from './compare.mjs'
export const conjugate = {number: () => identity}
export const conjugate = {'T:number': identitySubTypes('number')}
export const gcd = gcdType('NumInt')
export {invert} from './invert.mjs'
export {isZero} from './isZero.mjs'

View File

@ -1,3 +1,6 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const negate = {number: () => n => -n}
export const negate = {
'T:number': ({T}) => Returns(T, n => -n)
}

View File

@ -1,3 +1,5 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const one = {number: () => () => 1}
export const one = {number: () => Returns('NumInt', () => 1)}

View File

@ -1,15 +1,10 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
const intquotient = () => (n,d) => {
if (d === 0) return d
return Math.floor(n/d)
}
export const quotient = {
// Hmmm, seem to need all of these because of the generic template version
// Should be a way around that
'NumInt,NumInt': intquotient,
'NumInt,number': intquotient,
'number,NumInt': intquotient,
'number,number': intquotient
'T:number,T': () => Returns('NumInt', (n,d) => {
if (d === 0) return d
return Math.floor(n/d)
})
}

View File

@ -1,8 +1,10 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const roundquotient = {
'number,number': () => (n,d) => {
'number,number': () => Returns('NumInt', (n,d) => {
if (d === 0) return d
return Math.round(n/d)
}
})
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const sqrt = {
@ -5,13 +6,13 @@ export const sqrt = {
config,
'complex(number,number)': cplx,
'negate(number)': neg}) => {
if (config.predictable || !cplx) {
return n => isNaN(n) ? NaN : Math.sqrt(n)
if (config.predictable || !cplx) {
return Returns('number', n => isNaN(n) ? NaN : Math.sqrt(n))
}
return Returns('number|Complex<number>', n => {
if (isNaN(n)) return NaN
if (n >= 0) return Math.sqrt(n)
return cplx(0, Math.sqrt(neg(n)))
})
}
return n => {
if (isNaN(n)) return NaN
if (n >= 0) return Math.sqrt(n)
return cplx(0, Math.sqrt(neg(n)))
}
}
}

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const zero = {number: () => () => 0}
export const zero = {number: () => Returns('NumInt', () => 0)}

View File

@ -1,11 +1,13 @@
/* Note this is not a good algorithm for computing binomial coefficients,
import Returns from '../core/Returns.mjs'
/* Note this is _not_ a good algorithm for computing binomial coefficients,
* it's just for demonstration purposes
*/
export const choose = {
'NumInt,NumInt': ({factorial}) => (n,k) => Number(
factorial(n) / (factorial(k)*factorial(n-k))),
'NumInt,NumInt': ({factorial}) => Returns(
'NumInt', (n,k) => Number(factorial(n) / (factorial(k)*factorial(n-k)))),
'bigint,bigint': ({
factorial
}) => (n,k) => factorial(n) / (factorial(k)*factorial(n-k))
}) => Returns('bigint', (n,k) => factorial(n) / (factorial(k)*factorial(n-k)))
}

View File

@ -1,8 +1,15 @@
export function factorial(n) {
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) {
n = BigInt(n)
let prod = 1n
for (let i = n; i > 1n; --i) {
prod *= i
}
return prod
}
})
export {factorial}

View File

@ -1,3 +1,4 @@
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
@ -5,26 +6,28 @@ 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
'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
/* 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<bigint>': () => Returns('Complex<bigint>', x => x),
number: ({'equalTT(number,number)': eq}) => n => {
number: ({'equalTT(number,number)': eq}) => Returns('NumInt', n => {
if (eq(n, Math.round(n))) return Math.round(n)
return Math.floor(n)
},
}),
'Complex<T>': Complex.promoteUnary['Complex<T>'],
// OK to include a type totally not in Pocomath yet, it'll never be
// activated.
// Fraction: ({quotient}) => f => quotient(f.n, f.d), // oops have that now
BigNumber: ({
'round(BigNumber)': rnd,
'equal(BigNumber,BigNumber)': eq
}) => x => eq(x,round(x)) ? round(x) : x.floor()
}) => Returns('BigNumber', x => eq(x,round(x)) ? round(x) : x.floor())
}

View File

@ -1,25 +1,24 @@
/* 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<T>', {
// We are assuming that any 'Type<T>' 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)),
// 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<T>
// 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<T>:
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<T>:
// And we need there to be a way to determine the (instantiation)
// type of an tuple (that has already passed the base test):
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<T>:
from: {
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
// Here since there is no U it's a straight conversion:
@ -35,50 +34,66 @@ Tuple.promoteUnary = {
'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.
}) => {
const compType = me.fromInstance.joinTypes(
returnTypeOf(me).split('|'), 'convert')
return Returns(
`Tuple<${compType}>`, t => tuple(...(t.elts.map(x => me(x)))))
}
}
Tuple.promoteBinaryUnary = {
'Tuple<T>,Tuple<T>': ({'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
'Tuple<T>,Tuple<T>': ({'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
}
if (i < t.elts.length) result.push(meU(t.elts[i]))
else break
}
return tuple(...result)
return tuple(...result)
})
}
}
Tuple.promoteBinary = {
'Tuple<T>,Tuple<T>': ({'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<T>,Tuple<T>': ({'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.promoteBinaryStrict = {
'Tuple<T>,Tuple<T>': ({'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)
'Tuple<T>,Tuple<T>': ({'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)
})
}
}

View File

@ -1,11 +1,16 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Tuple.mjs'
export const equalTT = {
'Tuple<T>,Tuple<T>': ({'self(T,T)': me, 'length(Tuple)': len}) => (s,t) => {
'Tuple<T>,Tuple<T>': ({
'self(T,T)': me,
'length(Tuple)': len
}) => Returns('boolean', (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
}
})
}

View File

@ -1,7 +1,10 @@
import Returns from '../core/Returns.mjs'
export {Tuple} from './Types/Tuple.mjs'
export const isZero = {
'Tuple<T>': ({'self(T)': me}) => t => t.elts.every(e => me(e))
'Tuple<T>': ({'self(T)': me}) => Returns(
'boolean', 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`

View File

@ -1,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export {Tuple} from './Types/Tuple.mjs'
export const length = {Tuple: () => t => t.elts.length}
export const length = {'Tuple<T>': () => Returns('NumInt', t => t.elts.length)}

View File

@ -1,6 +1,10 @@
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': () => args => ({elts: args})}
export const tuple = {
'...T': ({T}) => Returns(`Tuple<${T}>`, args => ({elts: args}))
}

View File

@ -20,9 +20,56 @@ describe('The default full pocomath instance "math"', () => {
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex<number>')
})
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<number>'), 'Complex<number>')
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<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(
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<NumInt>'), '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<Complex<NumInt>>')
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')
})
it('can subtract numbers', () => {
assert.strictEqual(math.subtract(12, 5), 7)
//assert.strictEqual(math.subtract(3n, 1.5), 1.5)
assert.throws(() => math.subtract(3n, 1.5), 'TypeError')
})
it('can add numbers', () => {

View File

@ -39,8 +39,8 @@ 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)))
})
@ -83,6 +83,7 @@ describe('complex', () => {
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<number>)
assert.strictEqual(math.abs(q0), Math.sqrt(2))
assert.strictEqual(math.abs(q1), Math.sqrt(33)/4)
})

8
test/core/_utils.mjs Normal file
View File

@ -0,0 +1,8 @@
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(''), [])
})
})

View File

@ -135,13 +135,8 @@ describe('A custom instance', () => {
assert.strictEqual(
inst.typeMerge(3, inst.complex(4.5,2.1)),
'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
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...
assert.throws(
() => inst.typeMerge(3, inst.complex(3n)), TypeError)
inst.install(genericSubtract)
assert.throws(() => inst.typeMerge(3, undefined), TypeError)
})

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')
})
})

View File

@ -8,14 +8,12 @@ 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.deepStrictEqual(
math.tuple(3, math.complex(2n), 5.2),
{elts: [math.complex(3), math.complex(2n), math.complex(5.2)]})
assert.throws(
() => math.tuple(3, math.complex(2n), 5.2),
/TypeError.*unif/)
})
it('can be tested for zero and equality', () => {
@ -56,6 +54,9 @@ 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<bigint>,Tuple<bigint>'),
'Tuple<bigint>')
assert.throws(
() => math.subtract(math.tuple(5,6), math.tuple(7)),
/RangeError/)
@ -106,9 +107,16 @@ describe('tuple', () => {
})
it('supports sqrt', () => {
const mixedTuple = math.tuple(2, math.complex(0,2), 1.5)
assert.deepStrictEqual(
math.sqrt(math.tuple(4,-4,2.25)),
math.tuple(2, math.complex(0,2), 1.5))
mixedTuple,
math.tuple(math.complex(2), math.complex(0,2), math.complex(1.5)))
assert.strictEqual(
math.returnTypeOf('tuple', 'NumInt, Complex<NumInt>, number'),
'Tuple<Complex<number>>')
assert.deepStrictEqual(math.sqrt(math.tuple(4,-4,2.25)), mixedTuple)
assert.strictEqual(
math.returnTypeOf('sqrt', 'Tuple<NumInt>'), 'Tuple<Complex<number>>')
})
})