diff --git a/.gitignore b/.gitignore index 0dc139f..d0c9d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,9 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +# Webstorm +.idea + # yarn v2 .yarn/cache .yarn/unplugged diff --git a/README.md b/README.md index 34649c2..d15fcc4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # typocomath -A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. \ No newline at end of file +A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. + +To build and run the prototype, run: + +``` +npx tsc +node obj +``` diff --git a/src/Complex/all.ts b/src/Complex/all.ts index 9d417b8..3ff6311 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,8 +1,6 @@ -import {ForType} from '../core/Dispatcher.js' import * as Complex from './native.js' +import * as complex from './arithmetic.js' + +export { complex } export {Complex} - -declare module "../core/Dispatcher" { - interface ImplementationTypes extends ForType<'Complex', typeof Complex> {} -} diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts new file mode 100644 index 0000000..90c8eab --- /dev/null +++ b/src/Complex/arithmetic.ts @@ -0,0 +1,101 @@ +import {Complex} from './type.js' +import type { + Dependencies, Signature, Returns, RealType, AliasOf +} from '../interfaces/type.js' + +declare module "../interfaces/type" { + interface Signatures { + addReal: AliasOf<'add', (a: T, b: RealType) => T> + divideReal: AliasOf<'divide', (a: T, b: RealType) => T> + } +} + +export const add = + (dep: Dependencies<'add' | 'complex', T>): Signature<'add', Complex> => + (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) + +export const addReal = + (dep: Dependencies<'addReal' | 'complex', T>): + Signature<'addReal', Complex> => + (z, r) => dep.complex(dep.addReal(z.re, r), z.im) + +export const unaryMinus = + (dep: Dependencies<'unaryMinus' | 'complex', T>): + Signature<'unaryMinus', Complex> => + z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + +export const conj = + (dep: Dependencies<'unaryMinus' | 'conj' | 'complex', T>): + Signature<'conj', Complex> => + z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im)) + +export const subtract = + (dep: Dependencies<'subtract' | 'complex', T>): + Signature<'subtract', Complex> => + (w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) + +export const multiply = + (dep: Dependencies< + 'add' | 'subtract' | 'multiply' | 'conj' | 'complex', T>): + Signature<'multiply', Complex> => + (w, z) => { + const mult = dep.multiply + const realpart = dep.subtract( + mult( w.re, z.re), mult(dep.conj(w.im), z.im)) + const imagpart = dep.add( + mult(dep.conj(w.re), z.im), mult( w.im, z.re)) + return dep.complex(realpart, imagpart) + } + +export const absquare = + (dep: Dependencies<'absquare', T> + & Dependencies<'add', Returns<'absquare', T>>): + Signature<'absquare', Complex> => + z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + +export const divideReal = + (dep: Dependencies<'divideReal' | 'complex', T>): + Signature<'divideReal', Complex> => + (z, r) => dep.complex(dep.divideReal(z.re, r), dep.divideReal(z.im, r)) + +export const reciprocal = + (dep: Dependencies<'conj' | 'absquare' | 'divideReal', Complex>): + Signature<'reciprocal', Complex> => + z => dep.divideReal(dep.conj(z), dep.absquare(z)) + +export const divide = + (dep: Dependencies<'multiply' | 'reciprocal', Complex>): + Signature<'divide', Complex> => + (w, z) => dep.multiply(w, dep.reciprocal(z)) + +// The dependencies are slightly tricky here, because there are three types +// involved: Complex, T, and RealType, all of which might be different, +// and we have to get it straight which operations we need on each type, and +// in fact, we need `addReal` on both T and Complex, hence the dependency +// with a custom name, not generated via Dependencies<...> +export const sqrt = + (dep: Dependencies<'equal' | 'conservativeSqrt' | 'unaryMinus', RealType> + & Dependencies<'zero' | 'complex', T> + & Dependencies<'absquare' | 're' | 'divideReal', Complex> + & { + addTR: Signature<'addReal', T>, + addRR: Signature<'add', RealType>, + addCR: Signature<'addReal', Complex> + }): + Signature<'sqrt', Complex> => + z => { + const myabs = dep.conservativeSqrt(dep.absquare(z)) + const r = dep.re(z) + const negr = dep.unaryMinus(r) + if (dep.equal(myabs, negr)) { + // pure imaginary square root; z.im already zero + return dep.complex( + dep.zero(z.re), dep.addTR(z.im, dep.conservativeSqrt(negr))) + } + const num = dep.addCR(z, myabs) + const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r)) + const denom = dep.conservativeSqrt(denomsq) + return dep.divideReal(num, denom) + } + +export const conservativeSqrt = sqrt diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts new file mode 100644 index 0000000..ffcefaa --- /dev/null +++ b/src/Complex/predicate.ts @@ -0,0 +1,9 @@ +import {Complex} from './type.js' +import type {Dependencies, Signature} from '../interfaces/type.js' + +export const isReal = + (dep: Dependencies<'add' | 'equal' | 'isReal', T>): + Signature<'isReal', Complex> => + z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) + +export const isSquare: Signature<'isSquare', Complex> = z => true // FIXME: not correct for Complex once we get there diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts new file mode 100644 index 0000000..78550d7 --- /dev/null +++ b/src/Complex/relational.ts @@ -0,0 +1,6 @@ +import {Complex} from './type.js' +import {Dependencies, Signature} from '../interfaces/type.js' + +export const equal = + (dep: Dependencies<'equal', T>): Signature<'equal', Complex> => + (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index affbedc..50a1314 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,22 +1,63 @@ -import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js' +import {joinTypes, typeOfDependency} from '../core/Dispatcher.js' +import type { + ZeroType, OneType, NaNType, Dependencies, Signature, Returns +} from '../interfaces/type.js' -export type Complex = {re: T; im: T;} +export type Complex = { re: T; im: T; } export const Complex_type = { - test: (dep: {testT: (z: unknown) => z is T}) => + test: (dep: { testT: (z: unknown) => z is T }) => (z: unknown): z is Complex => - typeof z === 'object' && 're' in z && 'im' in z - && dep.testT(z.re) && dep.testT(z.im), + typeof z === 'object' && z != null && 're' in z && 'im' in z + && dep.testT(z['re']) && dep.testT(z['im']), infer: (dep: typeOfDependency) => (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), from: { - T: (dep: Dependency<'zero', [T]>) => (t: T) => - ({re: t, im: dep.zero(t)}), - Complex: (dep: {convert: (from: U) => T}) => - (z: Complex) => ({re: dep.convert(z.re), im: dep.convert(z.im)}) + T: (dep: Dependencies<'zero', T>) => (t: T) => + ({ re: t, im: dep.zero(t) }), + Complex: (dep: { convert: (from: U) => T }) => + (z: Complex) => ({ re: dep.convert(z.re), im: dep.convert(z.im) }) } } -export const complex_unary = (dep: Dependency<'zero', [T]>) => - (t: T) => ({re: t, im: dep.zero(t)}) -export const complex_binary = (t: T, u: T) => ({re: t, im: u}) +declare module "../interfaces/type" { + interface AssociatedTypes { + Complex: T extends Complex ? { + type: Complex + zero: Complex> + one: Complex | ZeroType> + nan: Complex> + real: RealType + } : never + } + + interface Signatures { + complex: ((re: T) => Complex) | ((re: T, im: T) => Complex) + } +} + +export const complex = + (dep: Dependencies<'zero', T>): Signature<'complex', T> => + (a, b) => ({re: a, im: b || dep.zero(a)}) + +export const zero = + (dep: Dependencies<'zero', T> + & Dependencies<'complex', Returns<'zero', T>>): + Signature<'zero', Complex> => + z => dep.complex(dep.zero(z.re), dep.zero(z.im)) + +export const one = + (dep: Dependencies<'one' | 'zero', T> + & Dependencies<'complex', Returns<'one' | 'zero', T>>): + Signature<'one', Complex> => + z => dep.complex(dep.one(z.re), dep.zero(z.im)) + +export const nan = + (dep: Dependencies<'nan', T> + & Dependencies<'complex', Returns<'nan', T>>): + Signature<'nan', Complex> => + z => dep.complex(dep.nan(z.re), dep.nan(z.im)) + +export const re = + (dep: Dependencies<'re', T>): Signature<'re', Complex> => + z => dep.re(z.re) diff --git a/src/all.ts b/src/all.ts index 192c7be..e2e83f1 100644 --- a/src/all.ts +++ b/src/all.ts @@ -1,2 +1,3 @@ export * from './numbers/all.js' export * from './Complex/all.js' +export * from './generic/all.js' diff --git a/src/core/Config.ts b/src/core/Config.ts index 3765328..c1eb24c 100644 --- a/src/core/Config.ts +++ b/src/core/Config.ts @@ -1,4 +1,5 @@ export type Config = { + epsilon: number predictable: boolean } diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index bed8f0d..def1bd3 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -10,44 +10,18 @@ type TypeName = string type Parameter = TypeName type Signature = Parameter[] +type DependenciesType = Record -export interface ImplementationTypes {} +// A "canned" dependency for a builtin function: export type typeOfDependency = {typeOf: (x: unknown) => TypeName} -// Helper for collecting implementations -// (Really just suffixes the type name onto the keys of exports) -export type ForType = keyof Exports extends string - ? {[K in keyof Exports as `${K}_${T}`]: Exports[K]} - : never - +// Utility needed in type definitions //dummy implementation for now export function joinTypes(a: TypeName, b: TypeName) { if (a === b) return a return 'any' } -/** - * Build up to Dependency type lookup - */ -type DependenciesType = Record - -type FinalShape = - FuncType extends (arg: DependenciesType) => Function - ? ReturnType : FuncType - -type BeginsWith = `${Name}${string}` - -type DependencyTypes = - {[K in keyof Ob]: K extends BeginsWith - ? FinalShape extends (...args: Params) => any - ? FinalShape - : never - : never} - -export type Dependency = - {[N in Name]: - DependencyTypes[keyof ImplementationTypes]} - // Now types used in the Dispatcher class itself type TypeSpecification = { diff --git a/src/generic/all.ts b/src/generic/all.ts new file mode 100644 index 0000000..78fa222 --- /dev/null +++ b/src/generic/all.ts @@ -0,0 +1 @@ +export * as generic from './native.js' diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts new file mode 100644 index 0000000..78ad57d --- /dev/null +++ b/src/generic/arithmetic.ts @@ -0,0 +1,5 @@ +import type {Dependencies, Signature} from '../interfaces/type.js' + +export const square = + (dep: Dependencies<'multiply', T>): Signature<'square', T> => + z => dep.multiply(z, z) diff --git a/src/generic/native.ts b/src/generic/native.ts new file mode 100644 index 0000000..b8290ae --- /dev/null +++ b/src/generic/native.ts @@ -0,0 +1,2 @@ +export * from './arithmetic.js' +export * from './relational.js' diff --git a/src/generic/relational.ts b/src/generic/relational.ts new file mode 100644 index 0000000..714cb49 --- /dev/null +++ b/src/generic/relational.ts @@ -0,0 +1,5 @@ +import {Dependencies, Signature} from '../interfaces/type.js' + +export const unequal = + (dep: Dependencies<'equal', T>): Signature<'unequal', T> => + (x, y) => !dep.equal(x, y) diff --git a/src/index.ts b/src/index.ts index bb83486..297b271 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,19 @@ import {Dispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' export default new Dispatcher(Specifications) + +import {Complex} from './Complex/type.js' +import {absquare as absquare_complex} from './Complex/arithmetic.js' + +const mockRealAdd = (a: number, b: number) => a+b +const mockComplexAbsquare = (z: Complex) => z.re*z.re + z.im*z.im + +const quatAbsquare = absquare_complex({ + add: mockRealAdd, + absquare: mockComplexAbsquare +}) + +const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) +const typeTest: typeof myabs = 7 // check myabs is just a number + +console.log('Result is', myabs) diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts new file mode 100644 index 0000000..df2d765 --- /dev/null +++ b/src/interfaces/arithmetic.ts @@ -0,0 +1,18 @@ +import type {Complex} from '../Complex/type.js' +import type {RealType} from './type.js' + +declare module "./type" { + interface Signatures { + add: (a: T, b: T) => T + unaryMinus: (a: T) => T + conj: (a: T) => T + subtract: (a: T, b: T) => T + multiply: (a: T, b: T) => T + square: (a: T) => T + absquare: (a: T) => RealType + reciprocal: (a: T) => T + divide: (a: T, b: T) => T + conservativeSqrt: (a: T) => T + sqrt: (a: T)=> T extends Complex ? T : T | Complex + } +} diff --git a/src/interfaces/predicate.ts b/src/interfaces/predicate.ts new file mode 100644 index 0000000..93d3b4a --- /dev/null +++ b/src/interfaces/predicate.ts @@ -0,0 +1,10 @@ +// Warning: a module must have something besides just a "declare module" +// section; otherwise it is ignored. +export type UnaryPredicate = (a: T) => boolean + +declare module "./type" { + interface Signatures { + isReal: (a: T) => boolean + isSquare: (a: T) => boolean + } +} diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts new file mode 100644 index 0000000..e2aa3a1 --- /dev/null +++ b/src/interfaces/relational.ts @@ -0,0 +1,9 @@ +// Warning: a module must have something besides just a "declare module" +// section; otherwise it is ignored. +export type BinaryPredicate = (a: T, b: T) => T +declare module "./type" { + interface Signatures { + equal: (a: T, b: T) => boolean + unequal: (a: T, b: T) => boolean + } +} diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts new file mode 100644 index 0000000..9e6870d --- /dev/null +++ b/src/interfaces/type.ts @@ -0,0 +1,75 @@ +/***** + * Every typocomath type has some associated types; they need + * to be published in the following interface. The key is the + * name of the type, and within the subinterface for that key, + * the type of the 'type' property is the actual TypeScript type + * we are associating the other properties to. Note the interface + * is generic with one parameter, corresponding to the fact that + * typocomath currently only allows generic types with a single + * generic parameter. This way, AssociatedTypes can give the + * associated types for a generic type instantiated with SubType. + * That's not necessary for the 'undefined' type (or if you look in the + * `numbers` subdirectory, the 'number' type) or any concrete type, + * but that's OK, the generic parameter doesn't hurt in those cases. + ****/ + +export interface AssociatedTypes { + undefined: { + type: undefined + zero: undefined + one: undefined + nan: undefined + real: undefined + } +} + +type AssociatedTypeNames = keyof AssociatedTypes['undefined'] +type ALookup = { + [K in keyof AssociatedTypes]: + T extends AssociatedTypes[K]['type'] ? AssociatedTypes[K][Name] : never +}[keyof AssociatedTypes] + +// For everything to compile, zero and one must be subtypes of T: +export type ZeroType = ALookup & T +export type OneType = ALookup & T +// But I believe 'nan' really might not be, like I think we will have to use +// 'undefined' for the nan of 'bigint', as it has nothing at all like NaN, +// so don't force it: +export type NaNType = ALookup +export type RealType = ALookup + +/***** + * The global signature patterns for all operations need to be published in the + * following interface. Each key is the name of an operation (but note that + * the Dispatcher will automatically merge operations that have the same + * name when the first underscore `_` and everything thereafter is stripped). + * The type of each key should be an interface with two properties: 'params' + * whose type is the type of the parameter list for the operation, and + * 'returns' whose type is the return type of the operation on those + * parameters. These types are generic in a parameter type T which should + * be interpreted as the type that the operation is supposed to "primarily" + * operate on, although note that some of the parameters and/or return types + * may depend on T rather than be exactly T. + * So note that the example 're' below provides essentially the same + * information that e.g. + * `type ReOp = (t: T) => RealType` + * would, but in a way that is much easier to manipulate in TypeScript, + * and it records the name of the operation as 're' also by virtue of the + * key 're' in the interface. + ****/ +export interface Signatures { + zero: (a: T) => ZeroType + one: (a: T) => OneType + // nan needs to be able to operate on its own output for everything + // else to compile. That's why its parameter type is widened: + nan: (a: T | NaNType) => NaNType + re: (a: T) => RealType +} + +type SignatureKey = keyof Signatures + +export type Signature, T> = Signatures[Name] +export type Returns, T> = ReturnType[Name]> +export type Dependencies, T> = {[K in Name]: Signature} + +export type AliasOf = T & {aliasOf?: Name} diff --git a/src/numbers/all.ts b/src/numbers/all.ts index 5aea220..deb4a8e 100644 --- a/src/numbers/all.ts +++ b/src/numbers/all.ts @@ -1,8 +1,3 @@ -import {ForType} from '../core/Dispatcher.js' import * as numbers from './native.js' export {numbers} - -declare module "../core/Dispatcher" { - interface ImplementationTypes extends ForType<'numbers', typeof numbers> {} -} diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index e78d9ec..11da5c2 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,20 +1,25 @@ -import {configDependency} from '../core/Config.js' -import {Dependency} from '../core/Dispatcher.js' +import type {configDependency} from '../core/Config.js' +import type {Dependencies, Signature} from '../interfaces/type.js' + +export const add: Signature<'add', number> = (a, b) => a + b +export const unaryMinus: Signature<'unaryMinus', number> = a => -a +export const conj: Signature<'conj', number> = a => a +export const subtract: Signature<'subtract', number> = (a, b) => a - b +export const multiply: Signature<'multiply', number> = (a, b) => a * b +export const absquare: Signature<'absquare', number> = a => a * a +export const reciprocal: Signature<'reciprocal', number> = a => 1 / a +export const divide: Signature<'divide', number> = (a, b) => a / b + +const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a) +export const conservativeSqrt: Signature<'conservativeSqrt', number> = basicSqrt -export const add = (a: number, b: number) => a + b -export const unaryMinus = (a: number) => -a -export const subtract = (a: number, b: number) => a - b -export const multiply = (a: number, b: number) => a * b -export const divide = (a: number, b: number) => a / b export const sqrt = - (dep: configDependency - & Dependency<'complex', [number, number]>) => { - if (dep.config.predictable || !dep.complex) { - return (a: number) => isNaN(a) ? NaN : Math.sqrt(a) - } - return (a: number) => { - if (isNaN(a)) return NaN - if (a >= 0) return Math.sqrt(a) - return dep.complex(0, Math.sqrt(unaryMinus(a))) - } - } + (dep: configDependency & Dependencies<'complex', number>): + Signature<'sqrt', number> => { + if (dep.config.predictable || !dep.complex) return basicSqrt + return a => { + if (isNaN(a)) return NaN + if (a >= 0) return Math.sqrt(a) + return dep.complex(0, Math.sqrt(unaryMinus(a))) + } + } diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts new file mode 100644 index 0000000..03bd80f --- /dev/null +++ b/src/numbers/predicate.ts @@ -0,0 +1,4 @@ +import type {Signature} from '../interfaces/type.js' + +export const isReal: Signature<'isReal', number> = (a) => true +export const isSquare: Signature<'isSquare', number> = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts new file mode 100644 index 0000000..8f1ac92 --- /dev/null +++ b/src/numbers/relational.ts @@ -0,0 +1,21 @@ +import {configDependency} from '../core/Config.js' +import {Signature} from '../interfaces/type.js' + +const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 + +export const equal = + (dep: configDependency): Signature<'equal', number> => + (x, y) => { + const eps = dep.config.epsilon + if (eps === null || eps === undefined) return x === y + if (x === y) return true + if (isNaN(x) || isNaN(y)) return false + + if (isFinite(x) && isFinite(y)) { + const diff = Math.abs(x - y) + if (diff < DBL_EPSILON) return true + return diff <= Math.max(Math.abs(x), Math.abs(y)) * eps + } + + return false + } diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 67dbd29..11e5d1c 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,7 +1,25 @@ +import type { Signature } from '../interfaces/type.js' + export const number_type = { before: ['Complex'], test: (n: unknown): n is number => typeof n === 'number', - from: {string: s => +s} + from: { string: (s: string) => +s } } -export const zero = (a: number) => 0 +declare module "../interfaces/type" { + interface AssociatedTypes { + numbers: { + type: number + zero: 0 + one: 1 + nan: typeof NaN + real: number + } + } +} + +// I don't like the redundancy of repeating 'zero'; any way to eliminate that? +export const zero: Signature<'zero', number> = (a) => 0 +export const one: Signature<'one', number> = (a) => 1 +export const nan: Signature<'nan', number> = (a) => NaN +export const re: Signature<'re', number> = (a) => a