diff --git a/src/Complex/all.ts b/src/Complex/all.ts index b0ec237..f5369ec 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,3 +1,10 @@ +import {ForType} from '../core/Dispatcher.js' +import {ComplexReturn} from './type.js' import * as Complex from './native.js' export {Complex} + +declare module "../core/Dispatcher" { + interface ReturnTypes + extends ForType<'Complex', ComplexReturn> {} +} diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 941a654..8fc32fb 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,127 +1,139 @@ -import { Complex, complex_binary } from './type.js' +import {Complex, UnderlyingReal, complex_binary} from './type.js' +import { + BBinary, Dependency, ConservativeUnary, ConservativeBinary, ImpType +} from '../core/Dispatcher.js' + +declare module "./type" { + interface ComplexReturn { + add: ConservativeBinary> + addReal: Params extends [infer Z, infer R] + ? [R] extends [UnderlyingReal] ? Z : never + : never + unaryMinus: ConservativeUnary> + conj: ConservativeUnary> + subtract: ConservativeBinary> + multiply: ConservativeBinary> + absquare: Params extends [infer Z] + ? Z extends Complex ? UnderlyingReal : never + : never + reciprocal: ConservativeUnary> + divide: ConservativeBinary> + divideByReal: Params extends [infer Z, infer R] + ? [R] extends [UnderlyingReal] ? Z : never + : never + // square root that remains the same type + conservativeSqrt: ConservativeUnary> + // Same as conservativeSqrt for complex numbers: + sqrt: ConservativeUnary> + + // complex square root of the real type of a complex: + complexSqrt: Params extends [infer T] ? Complex : never + } +} export const add = - (dep: { - add: (a: T, b: T) => T - }) => - (w: Complex, z: Complex): Complex => - complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im)) + (dep: Dependency<'add', [T,T]>): + ImpType<'add', [Complex, Complex]> => + (w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im)) export const addReal = - (dep: { - addReal: (a: T, b: T) => T - }) => - (z: Complex, r: T): Complex => - complex_binary(dep.addReal(z.re, r), z.im) + (dep: Dependency<'addReal', [T, UnderlyingReal]>): + ImpType<'addReal', [Complex, UnderlyingReal]> => + (z, r) => complex_binary(dep.addReal(z.re, r), z.im) export const unaryMinus = - (dep: { - unaryMinus: (z: T) => T - }) => - (z: Complex): Complex => - complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + (dep: Dependency<'unaryMinus', [T]>): + ImpType<'unaryMinus', [Complex]> => + z => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) export const conj = - (dep: { - unaryMinus: (z: T) => T, - conj: (z: T) => T - }) => - (z: Complex): Complex => - complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) + (dep: Dependency<'unaryMinus'|'conj', [T]>): + ImpType<'conj', [Complex]> => + z => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) export const subtract = - (dep: { - subtract: (a: T, b: T) => T - }) => - (w: Complex, z: Complex): Complex => - complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) + (dep: Dependency<'subtract', [T,T]>): + ImpType<'subtract', [Complex, Complex]> => + (w, z) => complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) export const multiply = - (dep: { - add: (a: T, b: T) => T, - subtract: (a: T, b: T) => T, - multiply: (a: T, b: T) => T, - conj: (z: T) => T - }) => - (w: Complex, z: Complex): Complex => { - 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 complex_binary(realpart, imagpart) - } + (dep: Dependency<'add', [T,T]> + & Dependency<'subtract', [T,T]> + & Dependency<'multiply', [T,T]> + & Dependency<'conj', [T]>): + ImpType<'multiply', [Complex, 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 complex_binary(realpart, imagpart) + } export const absquare = - (dep: { - add: (a: T, b: T) => T, - absquare: (z: T) => T - }) => - (z: Complex): T => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + (dep: Dependency<'absquare', [T]> + & Dependency<'add', BBinary>>): + ImpType<'absquare', [Complex]> => + z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = - (dep: { - divideByReal: (a: T, b: T) => T - }) => - (z: Complex, r: T) => - complex_binary(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + (dep: Dependency<'divideByReal', [T, UnderlyingReal]>): + ImpType<'divideByReal', [Complex, UnderlyingReal]> => + (z, r) => complex_binary( + dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) export const reciprocal = - (dep: { - conj: (z: Complex) => Complex, - absquare: (z: Complex) => T, - divideByReal: (a: Complex, b: T) => Complex, - zero: (z: T) => T, - }) => - (z: Complex): Complex => dep.divideByReal(dep.conj(z), dep.absquare(z)) + (dep: Dependency<'conj', [Complex]> + & Dependency<'absquare', [Complex]> + & Dependency<'divideByReal', [Complex, UnderlyingReal]>): + ImpType<'reciprocal', [Complex]> => + z => dep.divideByReal(dep.conj(z), dep.absquare(z)) export const divide = - (dep: { - multiply: (a: Complex, b: Complex) => Complex, - reciprocal: (z: Complex) => Complex, - }) => - (w: Complex, z: Complex) => dep.multiply(w, dep.reciprocal(z)) + (dep: Dependency<'multiply', [Complex, Complex]> + & Dependency<'reciprocal', [Complex]>): + ImpType<'divide', [Complex, Complex]> => + (w, z) => dep.multiply(w, dep.reciprocal(z)) export const complexSqrt = - (dep: { - conservativeSqrt: (a: T) => T, - isSquare: (a: T) => boolean, - complex: (a: T) => Complex, - unaryMinus: (a: T) => T, - zero: (a: T) => T, - nan: (a: Complex) => Complex - }) => - (r: T): Complex => { - if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) - const negative = dep.unaryMinus(r) - if (dep.isSquare(negative)) { - return complex_binary( - 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)) + (dep: Dependency<'conservativeSqrt', [T]> + & Dependency<'isSquare', [T]> + & Dependency<'complex', [T]> + & Dependency<'unaryMinus', [T]> + & Dependency<'zero', [T]> + & Dependency<'nan', [Complex]>): ImpType<'complexSqrt', [T]> => + r => { + if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) + const negative = dep.unaryMinus(r) + if (dep.isSquare(negative)) { + return complex_binary( + 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: { - isReal: (z: Complex) => boolean, - complexSqrt: (a: T) => Complex, - conservativeSqrt: (a: T) => T, - absquare: (a: Complex) => T, - addReal: (a: Complex, b: T) => Complex, - divideByReal: (a: Complex, b: T) => Complex, - add: (a: T, b: T) => T, - re: (a: Complex) => T, - - }) => - (z: Complex) => { - 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 denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) - const denom = dep.conservativeSqrt(denomsq) - return dep.divideByReal(num, denom) - } + (dep: Dependency<'isReal', [Complex]> + & Dependency<'complexSqrt', [T]> + & Dependency<'absquare', [Complex]> + & Dependency<'conservativeSqrt', [UnderlyingReal]> + & Dependency<'addReal', [Complex,UnderlyingReal]> + & Dependency<'re', [Complex]> + & Dependency<'add', [UnderlyingReal,UnderlyingReal]> + & Dependency<'divideByReal', [Complex,UnderlyingReal]> + ): ImpType<'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 denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) + const denom = dep.conservativeSqrt(denomsq) + return dep.divideByReal(num, denom) + } export const conservativeSqrt = sqrt diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index 47365fb..eafc5ad 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,12 +1,19 @@ -import { Complex } from './type.js' +import {Complex} from './type.js' +import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' + +declare module "./type" { + interface ComplexReturn { + isReal: Signature], boolean> + isSquare: Signature], boolean> + } +} export const isReal = - (dep: { - equal: (a: T, b: T) => boolean, - add: (a: T, b: T) => T, - isReal: (z: T) => boolean - }) => - (z: Complex) => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) + (dep: Dependency<'equal', [T,T]> + & Dependency<'add', [T,T]> + & Dependency<'isReal', [T]> + ): ImpType<'isReal', [Complex]> => + z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) -export const isSquare = - (z: Complex) => true // FIXME: not correct for Complex once we get there +export const isSquare: ImpType<'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 2621972..2a57dc4 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,7 +1,15 @@ -import { Complex } from './type.js' +import {Complex} from './type.js' +import {BBinary, ImpType, Dependency} from '../core/Dispatcher.js' + +declare module "./type" { + interface ComplexReturn { + equal: Params extends BBinary + ? B extends Complex ? boolean : never + : never + } +} export const equal = - (dep: { - equal: (a: T, b: T) => boolean - }) => - (w: Complex, z: Complex): boolean => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) + (dep: Dependency<'equal', [T,T]>): + ImpType<'equal', [Complex, 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 50a231f..36c040a 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,54 +1,87 @@ import { - joinTypes, typeOfDependency, Dependency, + joinTypes, typeOfDependency, Dependency, BBinary, ImpType, ImpReturns } from '../core/Dispatcher.js' -export type Complex = { re: T; im: T; } +export type Complex = {re: T; im: T;} + +export type UnderlyingReal = + T extends Complex ? UnderlyingReal : 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' && z != null && 're' in z && 'im' in z - && dep.testT(z.re) && dep.testT(z.im), + typeof z === 'object' && '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) }) + ({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: { - zero: (z: T) => Complex - }) => - (t: T) => ({ re: t, im: dep.zero(t) }) +export interface ComplexReturn { + // Sadly, I can't think of a way to make some nice abbreviation operators + // for these generic type specifications because TypeScript generics + // can't take and use generic parameters, only fully instantiated types. + complex: Params extends [infer U] ? Complex // unary case + : Params extends BBinary ? Complex // binary case + : never -export const complex_binary = - (t: T, u: T): Complex => ({ re: t, im: u }) + // alternatively if it seems better; each definition is simpler, but at + // the cost of having two keys here: + // complex_unary: Params extends [infer R] ? Complex : never + // complex_binary: Params extends BBinary ? Complex : never + + // There is actually a subtlety here that complex_unary really only works + // on real types that include their own zero value, so it should really be + // complex_unary: Params extends [infer R] + // ? ImpReturns<'zero', [R]> extends R ? Complex : never + // : never + // and that might actually simplify some of the typings of other operations, + // but we'll leave such fine tuning til later, if we adopt this scheme + + zero: Params extends [infer Z] // unary + ? Z extends Complex // of a Complex parameter + ? ImpReturns<'zero', T> extends T ? Z : never // that has its real 0 + : never + : never + one: Params extends [infer Z] // unary + ? Z extends Complex // of a Complex parameter + ? ImpReturns<'one'|'zero', T> extends T ? Z : never // has real 1, 0 + : never + : never + nan: Params extends [infer Z] // unary + ? Z extends Complex // of a Complex parameter + ? ImpReturns<'nan', T> extends T ? Z : never // has real NaN + : never + : never + re: Params extends [infer Z] + ? Z extends Complex ? UnderlyingReal : never + : never +} + +export const complex_unary = + (dep: Dependency<'zero', [T]>): ImpType<'complex', [T]> => + t => ({re: t, im: dep.zero(t)}) +export const complex_binary = (t: T, u: T): ImpReturns<'complex', [T,T]> => + ({re: t, im: u}) export const zero = - (dep: { - zero: (z: T) => T - }) => - (z: Complex): Complex => complex_binary(dep.zero(z.re), dep.zero(z.im)) + (dep: Dependency<'zero', [T]>): ImpType<'zero', [Complex]> => + z => complex_binary(dep.zero(z.re), dep.zero(z.im)) export const one = - (dep: { - zero: (z: T) => T, - one: (z: T) => T - }) => - (z: Complex): Complex => complex_binary(dep.one(z.re), dep.zero(z.im)) + (dep: Dependency<'zero' | 'one', [T]>): ImpType<'one', [Complex]> => + z => // Must provide parameter T, else TS narrows to return type of dep.one + complex_binary(dep.one(z.re), dep.zero(z.im)) export const nan = - (dep: { - nan: (z: T) => T - }) => - (z: Complex): Complex => complex_binary(dep.nan(z.re), dep.nan(z.im)) + (dep: Dependency<'nan', [T]>): ImpType<'nan', [Complex]> => + z => complex_binary(dep.nan(z.re), dep.nan(z.im)) export const re = - (dep: { - re: (z: T) => T - }) => - (z: Complex): T => dep.re(z.re) + (dep: Dependency<'re', [T]>): ImpType<'re', [Complex]> => + z => dep.re(z.re) diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 9b005d9..aa4c6a9 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -66,6 +66,30 @@ export interface ReturnTypes {} export type Signature = CandidateParams extends ActualParams ? Returns : never +// A homogeneous binary parameter tuple (comes up a lot, needs a better name?) +// Typical usage: `foo_impl: Params extends BBinary ? B : never` +// says that this implementation takes two arguments, both of type B, and +// returns the same type. +export type BBinary = [B, B] + +// A unary signature that preserves the type of its argument, which must +// extend the given Bound: +export type ConservativeUnary = + CandidateParams extends [infer T] ? T extends Bound ? T : never : never + +// A homogeneous binary signature that preserves the common type of its +// arguments, which must extend the given Bound: +export type ConservativeBinary = + CandidateParams extends BBinary + ? B extends Bound ? B : never + : never + +// Helper for collecting return types +// (Really just adds the literal string Suffix onto the keys of interface IFace) +export type ForType = keyof IFace extends string + ? {[K in keyof IFace as `${K}_${Suffix}`]: IFace[K]} + : never + //dummy implementation for now export function joinTypes(a: TypeName, b: TypeName) { if (a === b) return a diff --git a/src/generic/all.ts b/src/generic/all.ts index 1b1b8a4..1a008ac 100644 --- a/src/generic/all.ts +++ b/src/generic/all.ts @@ -1,3 +1,10 @@ +import { ForType } from '../core/Dispatcher.js' +import { GenericReturn } from './type.js' import * as generic from './arithmetic.js' export { generic } + +declare module "../core/Dispatcher" { + interface ReturnTypes + extends ForType<'generic', GenericReturn> { } +} diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index 99a7aab..46d6922 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,5 +1,39 @@ +import {Dependency, ImpType, ImpReturns} from "../core/Dispatcher"; + +declare module "./type" { + interface GenericReturn { + // Jos: not sure how to define this or why it is needed + // square: Signature + // square: ConservativeUnary + // square: Params extends [infer R] + // ? R extends number ? UnderlyingReal : never + // : never + + // The type of `square` in this interface, instantiated with the type + // Params of a parameter list, needs to be the return type of the + // operation `square` on those parameters. In other words, `square` gives + // a type transformer from the tuple type of its parameters to its return + // type. + // That's how Dispatcher knows what the return type will be in + // `Dependency<'square', [bigint]>`, for example: it instantiates + // GenericReturn with Params equal to [bigint] and then grabs the + // type of the `square` property. Hence we write: + + square: Params extends [infer T] // square only takes 1 arbitrary parameter + ? ImpReturns<'multiply', [T, T]> // and returns whatever multiply does + : never; // otherwise if not a single argument, this implementation + // doesn't handle it + + // If square had more than one implementation in this collection, we could + // either add more conditional clauses to the above type transformer + // as I did in Complex/type.ts for `complex`, or we could have two + // different keys that both start with `square_` and Dispatcher will + // check both (as I have now done in comments in Complex/type.ts and + // verified that also works). + } +} + export const square = - (dep: { - multiply: (x: T, y: T) => T - }) => - (z: T): T => dep.multiply(z, z) + (dep: Dependency<'multiply', [T, T]>): + ImpType<'square', [T]> => + z => dep.multiply(z, z) diff --git a/src/generic/type.ts b/src/generic/type.ts new file mode 100644 index 0000000..8589417 --- /dev/null +++ b/src/generic/type.ts @@ -0,0 +1,3 @@ +export interface GenericReturn { + +} \ No newline at end of file diff --git a/src/numbers/all.ts b/src/numbers/all.ts index deb4a8e..b034f25 100644 --- a/src/numbers/all.ts +++ b/src/numbers/all.ts @@ -1,3 +1,10 @@ +import {ForType} from '../core/Dispatcher.js' +import {NumbersReturn} from './type.js' import * as numbers from './native.js' export {numbers} + +declare module "../core/Dispatcher" { + interface ReturnTypes + extends ForType<'numbers', NumbersReturn> {} +} diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 499a942..b02b09f 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,28 +1,72 @@ -import { Config } from '../core/Config.js' -import type { Complex } from '../Complex/type.js' +import {configDependency} from '../core/Config.js' +import { + Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType +} from '../core/Dispatcher.js' +import type {Complex, UnderlyingReal} from '../Complex/type.js' -export const add = (a: number, b: number): number => a + b +declare module "./type" { + interface NumbersReturn { + // This description loses information: some subtypes like NumInt or + // Positive are closed under addition, but this says that the result + // of add is just a number, not still of the reduced type + // add: Signature + + // Whereas this one preserves information, but lies + // because it claims all subtypes of number are closed under addition, + // which is not true for `1 | 2 | 3`, for example. But because in + // generics that use add we often need to assign the result of add + // to something of the exact generic type, generics using add won't + // compile unless we lie in this way and assert that add returns + // the subtype. + add: ConservativeBinary + // Not sure how this will need to go when we introduce NumInt. + + addReal: Params extends [infer R, infer S] + ? R extends number ? S extends R ? R : never : never + : never + unaryMinus: ConservativeUnary + conj: ConservativeUnary + subtract: ConservativeBinary + multiply: ConservativeBinary + absquare: Params extends [infer R] + ? R extends number ? UnderlyingReal : never + : never + reciprocal: ConservativeUnary + divide: ConservativeBinary + divideByReal: Params extends [infer R, infer S] + ? R extends number ? S extends R ? R : never : never + : never + // best square root that remains the same type + conservativeSqrt: ConservativeUnary + // Best we can do for sqrt at compile time, since actual return + // type depends on config. Not sure how this will play out + // when we make a number-only bundle, but at least the import type + // above for Complex<> does not lead to any emitted JavaScript. + sqrt: Signature> + } +} + +export const add: ImpType<'add', [number, number]> = (a, b) => a + b export const addReal = add -export const unaryMinus = (a: number): number => -a -export const conj = (a: number): number => a -export const subtract = (a: number, b: number): number => a - b -export const multiply = (a: number, b: number): number => a * b -export const absquare = (a: number): number => a * a -export const reciprocal = (a: number): number => 1 / a -export const divide = (a: number, b: number): number => a / b -export const divideByReal = divide +export const unaryMinus: ImpType<'unaryMinus', [number]> = a => -a +export const conj: ImpType<'conj', [number]> = a => a +export const subtract: ImpType<'subtract', [number, number]> = (a, b) => a - b +export const multiply: ImpType<'multiply', [number, number]> = (a, b) => a * b +export const absquare: ImpType<'absquare', [number]> = a => a*a +export const reciprocal: ImpType<'reciprocal', [number]> = a => 1/a +export const divide: ImpType<'divide', [number, number]> = (a, b) => a / b +export const divideByReal: ImpType<'divideByReal', [number, number]> = divide -export const conservativeSqrt = (a: number): number => isNaN(a) ? NaN : Math.sqrt(a) +export const conservativeSqrt: ImpType<'conservativeSqrt', [number]> = + a => isNaN(a) ? NaN : Math.sqrt(a) export const sqrt = - (dep: { - config: Config, - complex: (re: number, im: number) => Complex - }): (a: number) => number | Complex => { - if (dep.config.predictable || !dep.complex) return conservativeSqrt - return a => { - if (isNaN(a)) return NaN - if (a >= 0) return Math.sqrt(a) - return dep.complex(0, Math.sqrt(unaryMinus(a))) - } - } + (dep: configDependency + & Dependency<'complex', [number, number]>): ImpType<'sqrt', [number]> => { + if (dep.config.predictable || !dep.complex) return conservativeSqrt + 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 index 483b103..b8cc4c5 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,2 +1,11 @@ -export const isReal = (a: number) : boolean => true -export const isSquare = (a: number) : boolean => a >= 0 +import {Signature, ImpType} from '../core/Dispatcher.js' + +declare module "./type" { + interface NumbersReturn { + isReal: Signature + isSquare: Signature + } +} + +export const isReal: ImpType<'isReal', [number]> = a => true +export const isSquare: ImpType<'isSquare', [number]> = a => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index dac949a..51d7e07 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,26 +1,34 @@ -import { Config } from '../core/Config.js' +import {configDependency} from '../core/Config.js' +import {Signature, ImpType, Dependency} from '../core/Dispatcher.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 -export const equal = - (dep: { - config: Config - }) => (x: number, y: number): boolean => { - 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 +declare module "./type" { + interface NumbersReturn { + equal: Signature + unequal: Signature + } +} - 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 +export const equal = + (dep: configDependency): ImpType<'equal', [number, 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 } - return false +export const unequal = (dep: Dependency<'equal', [number, number]>): + ImpType<'unequal', [number, number]> => + (x, y) => { + return !dep.equal(x, y) } - -export const unequal = (dep: { - equal: (x: number, y: number) => boolean -}) => - (x: number, y: number): boolean => !dep.equal(x, y) diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 46fbf6f..46971fc 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,10 +1,44 @@ +import {ImpType} from '../core/Dispatcher.js' +import type {UnderlyingReal} from '../Complex/type.js' + export const number_type = { before: ['Complex'], test: (n: unknown): n is number => typeof n === 'number', - from: { string: (s: string) => +s } + from: {string: s => +s} } -export const zero = (a: number): number => 0 -export const one = (a: number): number => 1 -export const nan = (a: number): number => NaN -export const re = (a: number): number => a + +export interface NumbersReturn { + // The following description of the return type of `zero` on a single + // number argument has ended up unfortunately rather complicated. However, + // it illustrates the typing is really working: Suppose we have a + // `type Small = 1 | 2 | 3`. Then Small indeed extends number, but we + // can't use the operation `zero(s: Small)` because zero is supposed to + // return something of the same type as its argument, but there is no + // zero in Small. Anyhow, in plain language the below says that given + // one parameter of a subtype of number, as long as that subtype includes 0, + // the zero operation returns a member of the type `0` (so we know even + // at compile time that its value will be 0). + zero: Params extends [infer T] + ? T extends number ? 0 extends T ? 0 : never : never + : never + // Note that in any case the simple + // zero: Signature + // makes complex fail to compile, because it worries that you might be + // making `Complex` where zero would not return the right type. + + one: Params extends [infer T] + ? T extends number ? 1 extends T ? 1 : never : never + : never + nan: Params extends [infer T] + ? T extends number ? typeof NaN extends T ? typeof NaN : never : never + : never + re: Params extends [infer T] + ? T extends number ? UnderlyingReal : never + : never +} + +export const zero: ImpType<'zero', [number]> = a => 0 +export const one: ImpType<'one', [number]> = a => 1 +export const nan: ImpType<'nan', [number]> = a => NaN +export const re: ImpType<'re', [number]> = a => a