feat: Allow self-reference in implementations (#4)

This PR also uses such self-reference to define negate and add
  for Complex numbers in a way that is independent of component types.

  Also adds a bigint type and verifies that pocomath will then handle
  Gaussian integers "for free".

  Ensures that if one function is invalidated, then any that depend on it will be.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #4
This commit is contained in:
Glen Whitney 2022-07-19 18:54:22 +00:00
parent 77b04fbdbb
commit 84a8b9d5c4
13 changed files with 132 additions and 21 deletions

View file

@ -1,14 +1,29 @@
import typed from 'typed-function'
/* Use a plain object with keys re and im for a complex */
typed.addType({
name: 'Complex',
test: z => z && typeof z === 'object' && 're' in z && 'im' in z
})
/* 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).
*/
export function isComplex(z) {
return z && typeof z === 'object' && 're' in z && 'im' in z
}
typed.addType({name: 'Complex', test: isComplex})
typed.addConversion({
from: 'number',
to: 'Complex',
convert: x => ({re: x, im: 0})
})
/* Pleasantly enough, it is OK to add this conversion even if there is no
* type 'bigint' defined, so everything should Just Work.
*/
typed.addConversion({
from: 'bigint',
to: 'Complex',
convert: x => ({re: x, im: 0n})
})
/* test if an entity is Complex<number>, so to speak: */
export function numComplex(z) {
return isComplex(z) && typeof z.re === 'number' && typeof z.im === 'number'
}

View file

@ -1,12 +1,15 @@
import './Complex.mjs'
import {numComplex} from './Complex.mjs'
export const add = {
'...Complex': [[], addends => {
const sum = {re: 0, im: 0}
addends.forEach(addend => {
sum.re += addend.re
sum.im += addend.im
})
return sum
'...Complex': [['self'], ref => addends => {
if (addends.length === 0) return {re:0, im:0}
const seed = addends.shift()
return addends.reduce((w,z) => {
/* Need a "base case" to avoid infinite self-reference loops */
if (numComplex(z) && numComplex(w)) {
return {re: w.re + z.re, im: w.im + z.im}
}
return {re: ref.self(w.re, z.re), im: ref.self(w.im, z.im)}
}, seed)
}]
}

View file

@ -1,2 +1,4 @@
export {complex} from './complex.mjs'
export {add} from './add.mjs'
export {negate} from './negate.mjs'
export {subtract} from '../generic/subtract.mjs'

View file

@ -1,6 +1,11 @@
import './Complex.mjs'
export const complex = {
'number, number': [[], (x, y) => ({re: x, im: y})],
/* Very permissive for sake of proof-of-concept; would be better to
* have a numeric/scalar type, e.g. by implementing subtypes in
* typed-function
*/
'any, any': [[], (x, y) => ({re: x, im: y})],
/* Take advantage of conversions in typed-function */
Complex: [[], z => z]
}

8
complex/negate.mjs Normal file
View file

@ -0,0 +1,8 @@
import {numComplex} from './Complex.mjs'
export const negate = {
/* need a "base case" to avoid infinite self-reference */
Complex: [['self'], ref => z => {
if (numComplex(z)) return {re: -z.re, im: -z.im}
return {re: ref.self(z.re), im: ref.self(z.im)}
}]
}