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:
parent
77b04fbdbb
commit
84a8b9d5c4
13 changed files with 132 additions and 21 deletions
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
8
complex/negate.mjs
Normal 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)}
|
||||
}]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue