diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index de0e16d..f6089f4 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,47 +1,45 @@ -import {Complex, ComplexOp} from './type.js' +import {Complex} from './type.js' import type { - AbsquareOp, AddOp, AddRealOp, ConjOp, ConservativeSqrtOp, DivideOp, - DivideByRealOp, MultiplyOp, ReciprocalOp, SqrtOp, SubtractOp, - UnaryMinusOp -} from '../interfaces/arithmetic.js' -import type { - NanOp, ReOp, ZeroOp, Depends, RealType, WithConstants, NaNType + Dependencies, OpType, OpReturns, RealType, ZeroType } from '../interfaces/type.js' -import type {IsSquareOp, IsRealOp} from '../interfaces/predicate.js' + +declare module "../interfaces/type" { + interface Operations { + // TODO: Make Dispatcher collapse operations that start with the same + // prefix up to a possible `_` + add_real: {params: [T, RealType], returns: T} + divide_real: {params: [T, RealType], returns: T} + } +} export const add = - (dep: Depends> & Depends>): AddOp> => + (dep: Dependencies<'add' | 'complex', T>): OpType<'add', Complex> => (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) -export const addReal = - (dep: Depends> & Depends>): - AddRealOp> => - (z, r) => dep.complex(dep.addReal(z.re, r), z.im) +export const add_real = + (dep: Dependencies<'add_real' | 'complex', T>): + OpType<'add_real', Complex> => + (z, r) => dep.complex(dep.add_real(z.re, r), z.im) export const unaryMinus = - (dep: Depends> & Depends>): - UnaryMinusOp> => + (dep: Dependencies<'unaryMinus' | 'complex', T>): + OpType<'unaryMinus', Complex> => z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) export const conj = - (dep: Depends> - & Depends> - & Depends>): - ConjOp> => + (dep: Dependencies<'unaryMinus' | 'conj' | 'complex', T>): + OpType<'conj', Complex> => z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im)) export const subtract = - (dep: Depends> & Depends>): - SubtractOp> => + (dep: Dependencies<'subtract' | 'complex', T>): + OpType<'subtract', Complex> => (w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) export const multiply = - (dep: Depends> - & Depends> - & Depends> - & Depends> - & Depends>): - MultiplyOp> => + (dep: Dependencies< + 'add' | 'subtract' | 'multiply' | 'conj' | 'complex', T>): + OpType<'multiply', Complex> => (w, z) => { const mult = dep.multiply const realpart = dep.subtract( @@ -52,69 +50,48 @@ export const multiply = } export const absquare = - (dep: Depends>> & Depends>): - AbsquareOp> => + (dep: Dependencies<'absquare', T> + & Dependencies<'add', OpReturns<'absquare', T>>): + OpType<'absquare', Complex> => z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = - (dep: Depends> & Depends>): - DivideByRealOp> => - (z, r) => dep.complex(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + (dep: Dependencies<'divide_real' | 'complex', T>): + OpType<'divide_real', Complex> => + (z, r) => dep.complex(dep.divide_real(z.re, r), dep.divide_real(z.im, r)) export const reciprocal = - (dep: Depends>> - & Depends>> - & Depends>>): - ReciprocalOp> => - z => dep.divideByReal(dep.conj(z), dep.absquare(z)) + (dep: Dependencies<'conj' | 'absquare' | 'divide_real', Complex>): + OpType<'reciprocal', Complex> => + z => dep.divide_real(dep.conj(z), dep.absquare(z)) export const divide = - (dep: Depends>> - & Depends>>): - DivideOp> => + (dep: Dependencies<'multiply' | 'reciprocal', Complex>): + OpType<'divide', Complex> => (w, z) => dep.multiply(w, dep.reciprocal(z)) -export type ComplexSqrtOp = { - op?: 'complexSqrt', - (a: T): Complex | NaNType>> -} -// Complex square root of a real type T -export const complexSqrt = - (dep: Depends> - & Depends> - & Depends> - & Depends>> - & Depends> - & Depends>>>): ComplexSqrtOp => - r => { - if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) - const negative = dep.unaryMinus(r) - if (dep.isSquare(negative)) { - return dep.complex(dep.zero(r), dep.conservativeSqrt(negative)) - } - // neither the real number or its negative is a square; could happen - // for example with bigint. So there is no square root. So we have to - // return the NaN of the type. - return dep.nan(dep.complex(r)) - } - export const sqrt = - (dep: Depends>> - & Depends> - & Depends>> - & Depends>> - & Depends>> - & Depends>> - & Depends>> - & Depends>>): SqrtOp> => + (dep: + Dependencies< + 'conservativeSqrt' | 'add' | 'unaryMinus' | 'equal', RealType> + & Dependencies<'zero' | 'add_real', T> + & Dependencies<'complex', T | ZeroType> + & Dependencies<'absquare' | 're' | 'divide_real', Complex> + & {add_complex_real: OpType<'add_real', Complex>}): + OpType<'sqrt', Complex> => z => { - if (dep.isReal(z)) return dep.complexSqrt(z.re) const myabs = dep.conservativeSqrt(dep.absquare(z)) - const num = dep.addReal(z, myabs) const r = dep.re(z) + const negr = dep.unaryMinus(r) + if (dep.equal(myabs, negr)) { + // pure imaginary square root; z.im already sero + return dep.complex( + dep.zero(z.re), dep.add_real(z.im, dep.conservativeSqrt(negr))) + } + const num = dep.add_complex_real(z, myabs) const denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) const denom = dep.conservativeSqrt(denomsq) - return dep.divideByReal(num, denom) + return dep.divide_real(num, denom) } export const conservativeSqrt = sqrt diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index cddd383..e6c4c7f 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,12 +1,9 @@ import {Complex} from './type.js' -import {EqualOp} from '../interfaces/relational.js' -import {AddOp} from '../interfaces/arithmetic.js' -import type {Depends} from '../interfaces/type.js' -import type {IsRealOp, IsSquareOp} from '../interfaces/predicate.js' +import type {Dependencies, OpType} from '../interfaces/type.js' export const isReal = - (dep: Depends> & Depends> & Depends>): - IsRealOp> => + (dep: Dependencies<'add' | 'equal' | 'isReal', T>): + OpType<'isReal', Complex> => z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) -export const isSquare: IsSquareOp> = z => true // FIXME: not correct for Complex once we get there +export const isSquare: OpType<'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 index 89db758..d0b9e52 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,7 +1,6 @@ import {Complex} from './type.js' -import {Depends} from '../interfaces/type.js' -import {EqualOp} from '../interfaces/relational.js' +import {Dependencies, OpType} from '../interfaces/type.js' export const equal = - (dep: Depends>): EqualOp> => + (dep: Dependencies<'equal', T>): OpType<'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 f995f92..8c669ea 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -2,7 +2,7 @@ import { joinTypes, typeOfDependency, Dependency, } from '../core/Dispatcher.js' import type { - OneOp, ZeroOp, NanOp, ReOp, ZeroType, OneType, NaNType, Depends + ZeroType, OneType, NaNType, Dependencies, OpType, OpReturns } from '../interfaces/type.js' export type Complex = { re: T; im: T; } @@ -32,31 +32,34 @@ declare module "../interfaces/type" { real: RealType } : never } + + interface Operations { + complex: {params: [T] | [T,T], returns: Complex} + } } -export type ComplexOp = {op?: 'complex', (a: T, b?: T): Complex} - export const complex = - (dep: Depends>): ComplexOp> => + (dep: Dependencies<'zero', T>): OpType<'complex', T | ZeroType> => (a, b) => ({re: a, im: b || dep.zero(a)}) export const zero = - (dep: Depends> & Depends>>): - ZeroOp> => + (dep: Dependencies<'zero', T> + & Dependencies<'complex', OpReturns<'zero', T>>): + OpType<'zero', Complex> => z => dep.complex(dep.zero(z.re), dep.zero(z.im)) export const one = - (dep: Depends> - & Depends> - & Depends|OneType>>): - OneOp> => + (dep: Dependencies<'one' | 'zero', T> + & Dependencies<'complex', OpReturns<'one' | 'zero', T>>): + OpType<'one', Complex> => z => dep.complex(dep.one(z.re), dep.zero(z.im)) export const nan = - (dep: Depends> & Depends>>): - NanOp> => + (dep: Dependencies<'nan', T> + & Dependencies<'complex', OpReturns<'nan', T>>): + OpType<'nan', Complex> => z => dep.complex(dep.nan(z.re), dep.nan(z.im)) export const re = - (dep: Depends>): ReOp> => + (dep: Dependencies<'re', T>): OpType<'re', Complex> => z => dep.re(z.re) diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index a7cf2b7..e0f55e8 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,6 +1,5 @@ -import type {Depends} from '../interfaces/type.js' -import type {MultiplyOp, SquareOp} from '../interfaces/arithmetic.js' +import type {Dependencies, OpType} from '../interfaces/type.js' export const square = - (dep: Depends>): SquareOp => + (dep: Dependencies<'multiply', T>): OpType<'square', T> => z => dep.multiply(z, z) diff --git a/src/generic/relational.ts b/src/generic/relational.ts index f47c392..2932e13 100644 --- a/src/generic/relational.ts +++ b/src/generic/relational.ts @@ -1,5 +1,5 @@ -import {Depends} from '../interfaces/type.js' -import type {EqualOp, UnequalOp} from '../interfaces/relational.js' +import {Dependencies, OpType} from '../interfaces/type.js' -export const unequal = (dep: Depends>): UnequalOp => +export const unequal = + (dep: Dependencies<'equal', T>): OpType<'unequal', T> => (x, y) => !dep.equal(x, y) diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts index 80e1155..1e4904e 100644 --- a/src/interfaces/arithmetic.ts +++ b/src/interfaces/arithmetic.ts @@ -1,25 +1,25 @@ import type {Complex} from '../Complex/type.js' import type {RealType, WithConstants, NaNType} from './type.js' -// Note: right now I've added an 'Op' suddix, -// so it is clear that the type holds the function type of an operation -// This is not necessary though, it is just a naming convention. -export type AddOp = {op?: 'add', (a: T, b: T): T} -export type AddRealOp = {op?: 'addReal', (a: T, b: RealType): T} -export type UnaryMinusOp = {op?: 'unaryMinus', (a: T): T} -export type ConjOp = {op?: 'conj', (a: T): T} -export type SubtractOp = {op?: 'subtract', (a: T, b: T): T} -export type MultiplyOp = {op?: 'multiply', (a: T, b: T): T} -export type AbsquareOp = {op?: 'absquare', (a: T): RealType} -export type ReciprocalOp = {op?: 'reciprocal', (a: T): T} -export type DivideOp = {op?: 'divide', (a: T, b: T): T} -export type DivideByRealOp = {op?: 'divideByReal', (a: T, b: RealType): T} -export type ConservativeSqrtOp = {op?: 'conservativeSqrt', (a: T): T} -export type SqrtOp = { - op?: 'sqrt', - (a: T): T extends Complex - ? Complex | NaNType>> - : T | Complex +type UnaryOperator = {params: [T], returns: T} +type BinaryOperator = {params: [T, T], returns: T} +declare module "./type" { + interface Operations { + add: BinaryOperator + unaryMinus: UnaryOperator + conj: UnaryOperator + subtract: BinaryOperator + multiply: BinaryOperator + square: UnaryOperator + absquare: {params: [T], returns: RealType} + reciprocal: UnaryOperator + divide: BinaryOperator + conservativeSqrt: UnaryOperator + sqrt: { + params: [T], + returns: T extends Complex + ? Complex> + : T | Complex + } + } } -export type SquareOp = {op?: 'square', (z: T): T} - diff --git a/src/interfaces/predicate.ts b/src/interfaces/predicate.ts index 37a69ea..16efd16 100644 --- a/src/interfaces/predicate.ts +++ b/src/interfaces/predicate.ts @@ -1,2 +1,9 @@ -export type IsRealOp = {op?: 'isReal', (a: T): boolean} -export type IsSquareOp = {op?: 'isSquare', (a: T): boolean} +// Warning: a module must have something besides just a "declare module" +// section; otherwise it is ignored. +export type UnaryPredicate = {params: [T], returns: boolean} +declare module "./type" { + interface Operations { + isReal: UnaryPredicate + isSquare: UnaryPredicate + } +} diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts index 29529ff..3149beb 100644 --- a/src/interfaces/relational.ts +++ b/src/interfaces/relational.ts @@ -1,2 +1,9 @@ -export type EqualOp = {op?: 'equal', (a: T, b: T): boolean} -export type UnequalOp = {op?: 'unequal', (a: T, b: T): boolean} +// Warning: a module must have something besides just a "declare module" +// section; otherwise it is ignored. +export type BinaryPredicate = {params: [T, T], returns: boolean} +declare module "./type" { + interface Operations { + equal: BinaryPredicate + unequal: BinaryPredicate + } +} diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index 1d7e103..8460235 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -1,3 +1,15 @@ +// Every typocomath type has some associated types; they need +// to be published as 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 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 either) or any concrete type, but that's OK, the +// generic parameter doesn't hurt in those cases. export interface AssociatedTypes { undefined: { type: undefined @@ -9,24 +21,46 @@ export interface AssociatedTypes { } type AssociatedTypeNames = keyof AssociatedTypes['undefined'] -export type Lookup = { +type ALookup = { [K in keyof AssociatedTypes]: T extends AssociatedTypes[K]['type'] ? AssociatedTypes[K][Name] : never }[keyof AssociatedTypes] -export type ZeroType = Lookup -export type OneType = Lookup +export type ZeroType = ALookup +export type OneType = ALookup export type WithConstants = T | ZeroType | OneType -export type NaNType = Lookup -export type RealType = Lookup +export type NaNType = ALookup +export type RealType = ALookup -export type ZeroOp = {op?: 'zero', (a: WithConstants): ZeroType} -export type OneOp = {op?: 'one', (a: WithConstants): OneType} -export type NanOp = {op?: 'nan', (a: T|NaNType): NaNType} -export type ReOp = {op?: 're', (a: T): RealType} +// 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 Operations { + zero: {params: [WithConstants], returns: ZeroType} + one: {params: [WithConstants], returns: OneType} + nan: {params: [T | NaNType], returns: NaNType} + re: {params: [T], returns: RealType} +} -type NamedFunction = {op?: string, (...params: any[]): any} -export type Depends = - {[K in FuncType['op']]: FuncType} +type OpKey = keyof Operations + +export type OpReturns = Operations[Name]['returns'] +export type OpType = + (...args: Operations[Name]['params']) => OpReturns +export type Dependencies = {[K in Name]: OpType} diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 6a05fe8..a902f0c 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,27 +1,21 @@ import type {configDependency} from '../core/Config.js' -import type {ComplexOp} from '../Complex/type.js' -import type { - AddOp, ConjOp, SubtractOp, UnaryMinusOp, MultiplyOp, - AbsquareOp, ReciprocalOp, DivideOp, ConservativeSqrtOp, SqrtOp -} from '../interfaces/arithmetic.js' -import type {Depends} from '../interfaces/type.js' +import type {Dependencies, OpType} from '../interfaces/type.js' -export const add: AddOp = (a, b) => a + b -export const addReal = add -export const unaryMinus: UnaryMinusOp = a => -a -export const conj: ConjOp = a => a -export const subtract: SubtractOp = (a, b) => a - b -export const multiply: MultiplyOp = (a, b) => a * b -export const absquare: AbsquareOp = a => a * a -export const reciprocal: ReciprocalOp = a => 1 / a -export const divide: DivideOp = (a, b) => a / b -export const divideByReal = divide +export const add: OpType<'add', number> = (a, b) => a + b +export const unaryMinus: OpType<'unaryMinus', number> = a => -a +export const conj: OpType<'conj', number> = a => a +export const subtract: OpType<'subtract', number> = (a, b) => a - b +export const multiply: OpType<'multiply', number> = (a, b) => a * b +export const absquare: OpType<'absquare', number> = a => a * a +export const reciprocal: OpType<'reciprocal', number> = a => 1 / a +export const divide: OpType<'divide', number> = (a, b) => a / b const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a) -export const conservativeSqrt: ConservativeSqrtOp = basicSqrt +export const conservativeSqrt: OpType<'conservativeSqrt', number> = basicSqrt export const sqrt = - (dep: configDependency & Depends>): SqrtOp => { + (dep: configDependency & Dependencies<'complex', number>): + OpType<'sqrt', number> => { if (dep.config.predictable || !dep.complex) return basicSqrt return a => { if (isNaN(a)) return NaN diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index 2018e56..ae065f8 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,4 +1,4 @@ -import type { IsRealOp, IsSquareOp } from '../interfaces/predicate.js' +import type {OpType} from '../interfaces/type.js' -export const isReal: IsRealOp = (a) => true -export const isSquare: IsSquareOp = (a) => a >= 0 +export const isReal: OpType<'isReal', number> = (a) => true +export const isSquare: OpType<'isSquare', number> = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index 5dbb3c5..6bb0597 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,12 +1,11 @@ -import {Config} from '../core/Config.js' -import type {EqualOp} from '../interfaces/relational.js' +import {configDependency} from '../core/Config.js' +import {OpType} from '../interfaces/type.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 export const equal = - (dep: { - config: Config - }): EqualOp => (x, y) => { + (dep: configDependency): OpType<'equal', number> => + (x, y) => { const eps = dep.config.epsilon if (eps === null || eps === undefined) return x === y if (x === y) return true diff --git a/src/numbers/type.ts b/src/numbers/type.ts index ff1ff61..a8e6dfa 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,4 @@ -import type { OneOp, ZeroOp, NanOp, ReOp } from '../interfaces/type.js' +import type { OpType } from '../interfaces/type.js' export const number_type = { before: ['Complex'], @@ -18,9 +18,8 @@ declare module "../interfaces/type" { } } -// I don't like the redundancy of repeating 'zero' and 'ZeroOp', any -// way to eliminate that? -export const zero: ZeroOp = (a) => 0 -export const one: OneOp = (a) => 1 -export const nan: NanOp = (a) => NaN -export const re: ReOp = (a) => a +// I don't like the redundancy of repeating 'zero'; any way to eliminate that? +export const zero: OpType<'zero', number> = (a) => 0 +export const one: OpType<'one', number> = (a) => 1 +export const nan: OpType<'nan', number> = (a) => NaN +export const re: OpType<'re', number> = (a) => a