fix(Types): Move distinct types into distinct identifiers

This allows types to be collected; prior to this commit they
   were conflicting from different modules.

   Uses this fix to extend sqrt to bigint, with the convention
   that it is undefined for non-perfect squares when 'predictable'
   is false and is the "best" approximation to the square root when
   'predictable' is true. Furthermore, for negative bigints, you might
   get a Gaussian integer when predictable is false; or you will just get
   your argument back when 'predictable' is true because what other
   bigint could you give back for a negative bigint?

   Also had to modify tests on the sign in sqrt(Complex) and add functions
   'zero' and 'one' to get types to match, as expected in #27.

   Adds numerous tests.

   Resolves #26.
   Resolves #27.
This commit is contained in:
Glen Whitney 2022-07-25 11:56:12 -07:00
parent b21d2b59fa
commit f68c7bd1fb
44 changed files with 287 additions and 109 deletions

View file

@ -1,6 +1,4 @@
export const Types = {
bigint: {
before: ['Complex'],
test: b => typeof b === 'bigint'
}
export const Type_bigint = {
before: ['Complex'],
test: b => typeof b === 'bigint'
}

View file

@ -1,4 +1,4 @@
export {Types} from './Types/bigint.mjs'
export * from './Types/bigint.mjs'
export const add = {
'...bigint': () => addends => addends.reduce((x,y) => x+y, 0n)

View file

@ -1,4 +1,8 @@
export {Types} from './Types/bigint.mjs'
export {add} from './add.mjs'
export {negate} from './negate.mjs'
export {subtract} from '../generic/subtract.mjs'
export * from './native.mjs'
export * from '../generic/arithmetic.mjs'
// resolve the conflicts
export {divide} from './divide.mjs'
export {multiply} from './multiply.mjs'
export {sign} from './sign.mjs'
export {sqrt} from './sqrt.mjs'

20
src/bigint/divide.mjs Normal file
View file

@ -0,0 +1,20 @@
export * from './Types/bigint.mjs'
export const divide = {
'bigint,bigint': ({config, 'sign(bigint)': sgn}) => {
if (config.predictable) {
return (n, d) => {
if (sgn(n) === sgn(d)) return n/d
const quot = n/d
if (quot * d == n) return quot
return quot - 1n
}
} else {
return (n, d) => {
const quot = n/d
if (quot * d == n) return quot
return undefined
}
}
}
}

5
src/bigint/multiply.mjs Normal file
View file

@ -0,0 +1,5 @@
export * from './Types/bigint.mjs'
export const multiply = {
'...bigint': () => multiplicands => multiplicands.reduce((x,y) => x*y, 1n)
}

9
src/bigint/native.mjs Normal file
View file

@ -0,0 +1,9 @@
export * from './Types/bigint.mjs'
export {add} from './add.mjs'
export {divide} from './divide.mjs'
export {multiply} from './multiply.mjs'
export {negate} from './negate.mjs'
export {one} from './one.mjs'
export {sign} from './sign.mjs'
export {sqrt} from './sqrt.mjs'
export {zero} from './zero.mjs'

View file

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

3
src/bigint/one.mjs Normal file
View file

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

9
src/bigint/sign.mjs Normal file
View file

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

30
src/bigint/sqrt.mjs Normal file
View file

@ -0,0 +1,30 @@
export * from './Types/bigint.mjs'
import isqrt from 'bigint-isqrt'
export const sqrt = {
bigint: ({config, complex, 'self(Complex)': complexSqrt}) => {
if (config.predictable) {
// 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)
}
if (!complexSqrt) {
return b => {
if (b >= 0n) {
const trial = isqrt(b)
if (trial * trial === b) return trial
}
return undefined
}
}
return b => {
if (b >= 0n) {
const trial = isqrt(b)
if (trial * trial === b) return trial
return undefined
}
return complexSqrt(complex(b))
}
}
}

3
src/bigint/zero.mjs Normal file
View file

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

View file

@ -2,21 +2,15 @@
* 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) {
function isComplex(z) {
return z && typeof z === 'object' && 're' in z && 'im' in z
}
export const Types = {
Complex: {
test: isComplex,
from: {
number: x => ({re: x, im: 0}),
bigint: x => ({re: x, im: 0n})
}
export const Type_Complex = {
test: isComplex,
from: {
number: x => ({re: x, im: 0}),
bigint: 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,4 +1,4 @@
export {Types} from './Types/Complex.mjs'
export * from './Types/Complex.mjs'
export const abs = {Complex: ({sqrt, add, multiply}) => z => {
return sqrt(add(multiply(z.re, z.re), multiply(z.im, z.im)))

View file

@ -1,4 +1,4 @@
export {Types} from './Types/Complex.mjs'
export * from './Types/Complex.mjs'
export const add = {
'...Complex': ({self}) => addends => {

View file

@ -1,2 +1,5 @@
export * from './native.mjs'
export * from '../generic/arithmetic.mjs'
export * from './native.mjs'
// resolve the conflicts
export {sqrt} from './sqrt.mjs'

View file

@ -1,11 +1,15 @@
export {Types} from './Types/Complex.mjs'
export * from './Types/Complex.mjs'
export * from '../generic/Types/generic.mjs'
export const complex = {
/* 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}),
'undefined': () => u => u,
'undefined,any': () => (u, y) => u,
'any,undefined': () => (x, u) => u,
'any,any': () => (x, y) => ({re: x, im: y}),
/* Take advantage of conversions in typed-function */
Complex: () => z => z
}

View file

@ -1,4 +1,4 @@
export {Types} from './Types/Complex.mjs'
export * from './Types/Complex.mjs'
export {abs} from './abs.mjs'
export {add} from './add.mjs'

View file

@ -1,4 +1,4 @@
export {Types} from './Types/Complex.mjs'
export * from './Types/Complex.mjs'
export const negate = {
Complex: ({self}) => z => ({re: self(z.re), im: self(z.im)})

View file

@ -1,35 +1,44 @@
export { Types } from './Types/Complex.mjs'
export * from './Types/Complex.mjs'
export const sqrt = {
Complex: ({
config,
zero,
sign,
one,
add,
complex,
multiply,
sign,
self,
divide,
add,
'abs(Complex)': abs,
subtract
}) => {
if (config.predictable) {
return z => {
const imZero = zero(z.im)
const imSign = sign(z.im)
const reOne = one(z.re)
const reSign = sign(z.re)
if (imSign === 0 && reSign === 1) return complex(self(z.re))
if (imSign === imZero && reSign === reOne) return complex(self(z.re))
const reTwo = add(reOne, reOne)
return complex(
multiply(sign(z.im), self(divide(add(abs(z),z.re), 2))),
self(divide(subtract(abs(z),z.re), 2))
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
self(divide(subtract(abs(z),z.re), reTwo))
)
}
}
return z => {
const imZero = zero(z.im)
const imSign = sign(z.im)
const reOne = one(z.re)
const reSign = sign(z.re)
if (imSign === 0 && reSign === 1) return self(z.re)
if (imSign === imZero && reSign === reOne) return self(z.re)
const reTwo = add(reOne, reOne)
const partial = add(abs(z), z.re)
return complex(
multiply(sign(z.im), self(divide(add(abs(z),z.re), 2))),
self(divide(subtract(abs(z),z.re), 2))
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
self(divide(subtract(abs(z),z.re), reTwo))
)
}
}

View file

@ -8,7 +8,8 @@ export default class PocomathInstance {
* in that if a new top-level PocomathInstance method is added, its name
* must be added to this list.
*/
static reserved = new Set(['config', 'importDependencies', 'install', 'name'])
static reserved = new Set([
'config', 'importDependencies', 'install', 'name', 'Types'])
constructor(name) {
this.name = name
@ -65,10 +66,11 @@ export default class PocomathInstance {
* operation in the body of the implementation. [NOTE: this signature-
* specific reference is not yet implemented.]
*
* Note that the "operation" named `Types` is special: it gives
* types that must be installed in the instance. In this case, the keys
* are type names, and the values are plain objects with the following
* properties:
* Note that any "operation" whose name begins with `Type_` is special:
* it defines a types that must be installed in the instance.
* The remainder of the "operation" name following the `_` is the
* name of the type. The value of the "operation" should be a plain
* object with the following properties:
*
* - test: the predicate for the type
* - from: a plain object mapping the names of types that can be converted
@ -78,8 +80,8 @@ export default class PocomathInstance {
*/
install(ops) {
for (const [item, spec] of Object.entries(ops)) {
if (item === 'Types') {
this._installTypes(spec)
if (item.slice(0,5) === 'Type_') {
this._installType(item.slice(5), spec)
} else {
this._installOp(item, spec)
}
@ -128,43 +130,40 @@ export default class PocomathInstance {
/* Used internally by install, see the documentation there.
* Note that unlike _installOp below, we can do this immediately
*/
_installTypes(typeSpecs) {
for (const [type, spec] of Object.entries(typeSpecs)) {
if (type in this.Types) {
if (spec !== this.Types[type]) {
throw new SyntaxError(
`Conflicting definitions of type ${type}`)
}
continue
_installType(type, spec) {
if (type in this.Types) {
if (spec !== this.Types[type]) {
throw new SyntaxError(`Conflicting definitions of type ${type}`)
}
let beforeType = 'any'
for (const other of spec.before || []) {
if (other in this.Types) {
beforeType = other
break
}
}
this._typed.addTypes([{name: type, test: spec.test}], beforeType)
/* Now add conversions to this type */
for (const from in (spec.from || {})) {
if (from in this.Types) {
this._typed.addConversion(
{from, to: type, convert: spec.from[from]})
}
}
/* And add conversions from this type */
for (const to in this.Types) {
if (type in (this.Types[to].from || {})) {
this._typed.addConversion(
{from: type, to, convert: this.Types[to].from[type]})
}
}
this.Types[type] = spec
// rebundle anything that uses the new type:
this._invalidateDependents(':' + type)
return
}
let beforeType = 'any'
for (const other of spec.before || []) {
if (other in this.Types) {
beforeType = other
break
}
}
this._typed.addTypes([{name: type, test: spec.test}], beforeType)
/* Now add conversions to this type */
for (const from in (spec.from || {})) {
if (from in this.Types) {
this._typed.addConversion(
{from, to: type, convert: spec.from[from]})
}
}
/* And add conversions from this type */
for (const to in this.Types) {
if (type in (this.Types[to].from || {})) {
this._typed.addConversion(
{from: type, to, convert: this.Types[to].from[type]})
}
}
this.Types[type] = spec
// rebundle anything that uses the new type:
this._invalidateDependents(':' + type)
}
/* Used internally by install, see the documentation there */
_installOp(name, implementations) {
if (name.charAt(0) === '_') {

View file

@ -0,0 +1,2 @@
export const Type_undefined = {test: u => u === undefined}

View file

@ -1,3 +1,7 @@
export * from './Types/generic.mjs'
export {multiply} from './multiply.mjs'
export {divide} from './divide.mjs'
export {sign} from './sign.mjs'
export {sqrt} from './sqrt.mjs'
export {subtract} from './subtract.mjs'

12
src/generic/multiply.mjs Normal file
View file

@ -0,0 +1,12 @@
export * from './Types/generic.mjs'
export const multiply = {
'undefined': () => u => u,
'undefined,...any': () => (u, rest) => u,
'any,undefined': () => (x, u) => u,
'any,undefined,...any': () => (x, u, rest) => u,
'any,any,undefined': () => (x, y, u) => u,
'any,any,undefined,...any': () => (x, y, u, rest) => u
// Bit of a hack since this should go on indefinitely...
}

3
src/generic/sqrt.mjs Normal file
View file

@ -0,0 +1,3 @@
export * from './Types/generic.mjs'
export const sqrt = {undefined: () => () => undefined}

View file

@ -1,8 +1,6 @@
export const Types = {
number: {
before: ['Complex'],
test: n => typeof n === 'number',
from: {string: s => +s}
}
export const Type_number = {
before: ['Complex'],
test: n => typeof n === 'number',
from: {string: s => +s}
}

View file

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

View file

@ -1,4 +1,4 @@
export {Types} from './Types/number.mjs'
export * from './Types/number.mjs'
export const add = {
'...number': () => addends => addends.reduce((x,y) => x+y, 0),

View file

@ -1,4 +1,6 @@
export {Types} from './Types/number.mjs'
export * from './native.mjs'
export * from '../generic/arithmetic.mjs'
export * from './native.mjs'
// resolve the conflicts
export {sqrt} from './sqrt.mjs'
export {multiply} from './multiply.mjs'

View file

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

View file

@ -1,4 +1,4 @@
export {Types} from './Types/number.mjs'
export * from './Types/number.mjs'
export const multiply = {
'...number': () => multiplicands => multiplicands.reduce((x,y) => x*y, 1),

View file

@ -1,4 +0,0 @@
export {Types} from './Types/number.mjs'
export {add} from './add.mjs'
export {negate} from './negate.mjs'
export {sqrt} from './sqrt.mjs'

View file

@ -1,8 +1,10 @@
export {Types} from './Types/number.mjs'
export * from './Types/number.mjs'
export {abs} from './abs.mjs'
export {add} from './add.mjs'
export {invert} from './invert.mjs'
export {multiply} from './multiply.mjs'
export {negate} from './negate.mjs'
export {one} from './one.mjs'
export {sqrt} from './sqrt.mjs'
export {zero} from './zero.mjs'

View file

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

3
src/number/one.mjs Normal file
View file

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

View file

@ -1,4 +1,4 @@
export { Types } from './Types/number.mjs'
export * from './Types/number.mjs'
export const sqrt = {
number: ({config, complex, 'self(Complex)': complexSqrt}) => {

3
src/number/zero.mjs Normal file
View file

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

View file

@ -1,7 +1,7 @@
/* Core of pocomath: generates the default instance */
import PocomathInstance from './core/PocomathInstance.mjs'
import * as numbers from './number/native.mjs'
import * as bigints from './bigint/all.mjs'
import * as bigints from './bigint/native.mjs'
import * as complex from './complex/native.mjs'
import * as generic from './generic/all.mjs'