From 1eb73be2fa2a89d2aa7aa26bf146047e566253b5 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 21 Dec 2022 00:18:42 -0500 Subject: [PATCH 1/7] refactor: entirely new scheme for specifying return types --- src/Complex/all.ts | 4 +- src/Complex/type.ts | 20 ++++++-- src/core/Dispatcher.ts | 98 ++++++++++++++++++++++++++++++--------- src/numbers/all.ts | 4 +- src/numbers/arithmetic.ts | 45 ++++++++++++++---- src/numbers/type.ts | 21 ++++++++- 6 files changed, 154 insertions(+), 38 deletions(-) diff --git a/src/Complex/all.ts b/src/Complex/all.ts index 9d417b8..f5369ec 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,8 +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 ImplementationTypes extends ForType<'Complex', typeof Complex> {} + interface ReturnTypes + extends ForType<'Complex', ComplexReturn> {} } diff --git a/src/Complex/type.ts b/src/Complex/type.ts index affbedc..a174c40 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,4 +1,6 @@ -import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js' +import { + joinTypes, typeOfDependency, Dependency, BBinary, ImpType, ImpReturns +} from '../core/Dispatcher.js' export type Complex = {re: T; im: T;} @@ -17,6 +19,16 @@ export const Complex_type = { } } -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}) +type Binary = [B, B] + +export interface ComplexReturn { + complex: Params extends [infer U] ? Complex // unary case + : Params extends BBinary ? Complex // binary case + : 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}) diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index bed8f0d..46a8bff 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -10,14 +10,68 @@ type TypeName = string type Parameter = TypeName type Signature = Parameter[] +type DependenciesType = Record -export interface ImplementationTypes {} 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]} +// All of the implementations must publish descriptions of their +// return types into the following interface, using the format +// described just below: +export interface ReturnTypes {} + +/***** + To describe one implementation for a hypothetical operation `foo`, there + should be a property of the interface whose name starts with `foo` and whose + next character, if any, is an underscore. The type of this property + must be the return type of that implementation when Params matches the + parameter types of the implementation, and `never` otherwise. + Thus to describe an implementation that takes a number and a string and + returns a boolean, for example, you could write + ``` + declare module "Dispatcher" { + interface ReturnTypes { + foo_example: Params extends [number, string] ? boolean : never + } + } + ``` + If there is another, generic implementation that takes one argument + of any type and returns a Vector of that type, you can say + ``` + ... + foo_generic: Params extends [infer T] ? Vector : never + ... + ``` + In practice, each subdirectory corresponding to a type, like Complex, + defines an interface, like `ComplexReturn` for the implementations + in that subdirectory, which can mostly be defined without suffixes because + there's typically just a single implementation within that domain. + Then the module responsible for collating all of the implementations for + that type inserts all of the properties of that interface into `ReturnTypes` + suitably suffixed to avoid collisions. + + One might think that simply defining an implementation for `foo` + of type `(n: number, s: string) => boolean` would provide all of the same + information as the type of the key `foo_example` in the ReturnTypes + interface above, but in practice TypeScript has challenges in extracting + types relating to functions. (In particular, there is no + way to get the specialized return type of a generic function when it is + called on aguments whose specific types match the generic parameters.) + Hence the need for this additional mechanism to specify return types, in + a way readily suited for TypeScript type computations. +*****/ + +// Helpers for specifying signatures + +// A homogenous binary operation (comes up a lot) +// 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] + +// 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 @@ -26,27 +80,27 @@ export function joinTypes(a: TypeName, b: TypeName) { return 'any' } -/** - * Build up to Dependency type lookup - */ -type DependenciesType = Record +// Used to filter keys that match a given operation name +type BeginsWith = Name | `${Name}_${string}` -type FinalShape = - FuncType extends (arg: DependenciesType) => Function - ? ReturnType : FuncType +// Look up the return type of an implementation based on its name +// and the parameters it takes +export type ImpReturns = + {[K in keyof ReturnTypes]: K extends BeginsWith + ? ReturnTypes[K] : never}[keyof ReturnTypes] -type BeginsWith = `${Name}${string}` - -type DependencyTypes = - {[K in keyof Ob]: K extends BeginsWith - ? FinalShape extends (...args: Params) => any - ? FinalShape - : never - : never} +// The type of an implementation (with dependencies satisfied, +// based on its name and the parameters it takes +export type ImpType = + (...args: Params) => ImpReturns +// The type of a dependency on an implementation based on its name +// and the parameters it takes (just a simple object with one property +// named the same as the operation, of value type equal to the type of +// that implementation. These can be `&`ed together in case of multiple +// dependencies: export type Dependency = - {[N in Name]: - DependencyTypes[keyof ImplementationTypes]} + {[N in Name]: ImpType} // Now types used in the Dispatcher class itself diff --git a/src/numbers/all.ts b/src/numbers/all.ts index 5aea220..b034f25 100644 --- a/src/numbers/all.ts +++ b/src/numbers/all.ts @@ -1,8 +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 ImplementationTypes extends ForType<'numbers', typeof numbers> {} + interface ReturnTypes + extends ForType<'numbers', NumbersReturn> {} } diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index e78d9ec..bc9b862 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,18 +1,45 @@ import {configDependency} from '../core/Config.js' -import {Dependency} from '../core/Dispatcher.js' +import {BBinary, Dependency, ImpType} from '../core/Dispatcher.js' +import type {Complex} from '../Complex/type.js' -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 +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: Params extends BBinary ? number : never + // Whereas this one would preserve information, but would lie + // because it claims all subtypes of number are closed under addition, + // which is not true for `1 | 2 | 3`, for example. + // add: Params extends BBinary + // ? B extends number ? B : never + // : never + // + // Not sure how this will need to go when we introduce NumInt. + unaryMinus: Params extends [number] ? number : never + subtract: Params extends BBinary ? number : never + multiply: Params extends BBinary ? number : never + divide: Params extends BBinary ? number : never + // 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: Params extends [number] ? (number | Complex) : never + } +} + +export const add: ImpType<'add', [number, number]> = (a, b) => a + b +export const unaryMinus: ImpType<'unaryMinus', [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 divide: ImpType<'divide', [number, number]> = (a, b) => a / b export const sqrt = (dep: configDependency - & Dependency<'complex', [number, number]>) => { + & Dependency<'complex', [number, number]>): ImpType<'sqrt', [number]> => { if (dep.config.predictable || !dep.complex) { - return (a: number) => isNaN(a) ? NaN : Math.sqrt(a) + return a => isNaN(a) ? NaN : Math.sqrt(a) } - return (a: number) => { + 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/type.ts b/src/numbers/type.ts index 67dbd29..a32b791 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,7 +1,26 @@ +import {ImpType} from '../core/Dispatcher.js' + export const number_type = { before: ['Complex'], test: (n: unknown): n is number => typeof n === 'number', from: {string: s => +s} } -export const zero = (a: number) => 0 + +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 +} + +export const zero: ImpType<'zero', [number]> = a => 0 From d55776655f3bbe64c909bb60c5cfe016f44a5c7d Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 21 Dec 2022 11:41:25 -0500 Subject: [PATCH 2/7] refactor: Convenience type operator for specifying concrete signatures --- src/Complex/type.ts | 5 +++-- src/core/Dispatcher.ts | 12 ++++++++---- src/numbers/arithmetic.ts | 14 +++++++------- src/numbers/type.ts | 4 ++++ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index a174c40..b0bb5b7 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -19,9 +19,10 @@ export const Complex_type = { } } -type Binary = [B, B] - 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 diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 46a8bff..b8d7c60 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -9,7 +9,7 @@ type TypeName = string type Parameter = TypeName -type Signature = Parameter[] +type InputSignature = Parameter[] type DependenciesType = Record export type typeOfDependency = {typeOf: (x: unknown) => TypeName} @@ -62,7 +62,11 @@ export interface ReturnTypes {} // Helpers for specifying signatures -// A homogenous binary operation (comes up a lot) +// A basic signature with concrete types +export type Signature = + CandidateParams extends ActualParams ? Returns : never + +// A homogenous binary operation (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. @@ -118,9 +122,9 @@ type SpecificationsGroup = Record export class Dispatcher { installSpecification( name: string, - signature: Signature, + signature: InputSignature, returns: TypeName, - dependencies: Record, + dependencies: Record, behavior: Function // possible todo: constrain this type based // on the signature, return type, and dependencies. Not sure if // that's really possible, though. diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index bc9b862..9bca0ab 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,5 +1,5 @@ import {configDependency} from '../core/Config.js' -import {BBinary, Dependency, ImpType} from '../core/Dispatcher.js' +import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' import type {Complex} from '../Complex/type.js' declare module "./type" { @@ -7,7 +7,7 @@ declare module "./type" { // 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: Params extends BBinary ? number : never + add: Signature // Whereas this one would preserve information, but would lie // because it claims all subtypes of number are closed under addition, // which is not true for `1 | 2 | 3`, for example. @@ -16,15 +16,15 @@ declare module "./type" { // : never // // Not sure how this will need to go when we introduce NumInt. - unaryMinus: Params extends [number] ? number : never - subtract: Params extends BBinary ? number : never - multiply: Params extends BBinary ? number : never - divide: Params extends BBinary ? number : never + unaryMinus: Signature + subtract: Signature + multiply: Signature + divide: Signature // 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: Params extends [number] ? (number | Complex) : never + sqrt: Signature> } } diff --git a/src/numbers/type.ts b/src/numbers/type.ts index a32b791..105ca46 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -21,6 +21,10 @@ export interface NumbersReturn { 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. } export const zero: ImpType<'zero', [number]> = a => 0 From fbec410c4252b82ad9de63a8a4bb3e48cd216f16 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 22 Dec 2022 00:14:58 -0500 Subject: [PATCH 3/7] feat: Implement complex arithmetic through sqrt Together with any auxiliary functions needed for that goal. Also strives to ensure the same functions are being defined for number and for `Complex`. --- src/Complex/arithmetic.ts | 139 ++++++++++++++++++++++++++++++++++++++ src/Complex/predicate.ts | 19 ++++++ src/Complex/relational.ts | 15 ++++ src/Complex/type.ts | 41 ++++++++++- src/core/Config.ts | 1 + src/core/Dispatcher.ts | 14 +++- src/numbers/arithmetic.ts | 57 +++++++++++----- src/numbers/predicate.ts | 11 +++ src/numbers/relational.ts | 27 ++++++++ src/numbers/type.ts | 14 ++++ 10 files changed, 320 insertions(+), 18 deletions(-) create mode 100644 src/Complex/arithmetic.ts create mode 100644 src/Complex/predicate.ts create mode 100644 src/Complex/relational.ts create mode 100644 src/numbers/predicate.ts create mode 100644 src/numbers/relational.ts diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts new file mode 100644 index 0000000..8fc32fb --- /dev/null +++ b/src/Complex/arithmetic.ts @@ -0,0 +1,139 @@ +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: 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: Dependency<'addReal', [T, UnderlyingReal]>): + ImpType<'addReal', [Complex, UnderlyingReal]> => + (z, r) => complex_binary(dep.addReal(z.re, r), z.im) + +export const unaryMinus = + (dep: Dependency<'unaryMinus', [T]>): + ImpType<'unaryMinus', [Complex]> => + z => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + +export const conj = + (dep: Dependency<'unaryMinus'|'conj', [T]>): + ImpType<'conj', [Complex]> => + z => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) + +export const subtract = + (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: 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: Dependency<'absquare', [T]> + & Dependency<'add', BBinary>>): + ImpType<'absquare', [Complex]> => + z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + +export const divideByReal = + (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: 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: Dependency<'multiply', [Complex, Complex]> + & Dependency<'reciprocal', [Complex]>): + ImpType<'divide', [Complex, Complex]> => + (w, z) => dep.multiply(w, dep.reciprocal(z)) + +export const complexSqrt = + (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: 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 new file mode 100644 index 0000000..eafc5ad --- /dev/null +++ b/src/Complex/predicate.ts @@ -0,0 +1,19 @@ +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: 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: 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 new file mode 100644 index 0000000..2a57dc4 --- /dev/null +++ b/src/Complex/relational.ts @@ -0,0 +1,15 @@ +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: 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 b0bb5b7..a150700 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -4,6 +4,9 @@ import { 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}) => (z: unknown): z is Complex => @@ -25,7 +28,26 @@ export interface ComplexReturn { // 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 + : never + + 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 = @@ -33,3 +55,20 @@ export const complex_unary = 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: Dependency<'zero', [T]>): ImpType<'zero', [Complex]> => + z => complex_binary(dep.zero(z.re), dep.zero(z.im)) + +export const one = + (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: Dependency<'nan', [T]>): ImpType<'nan', [Complex]> => + z => complex_binary(dep.nan(z.re), dep.nan(z.im)) + +export const re = + (dep: Dependency<'re', [T]>): ImpType<'re', [Complex]> => + z => dep.re(z.re) 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 b8d7c60..aa4c6a9 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -66,12 +66,24 @@ export interface ReturnTypes {} export type Signature = CandidateParams extends ActualParams ? Returns : never -// A homogenous binary operation (comes up a lot, needs a better name?) +// 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 diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 9bca0ab..b02b09f 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,25 +1,43 @@ import {configDependency} from '../core/Config.js' -import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' -import type {Complex} from '../Complex/type.js' +import { + Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType +} from '../core/Dispatcher.js' +import type {Complex, UnderlyingReal} from '../Complex/type.js' 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 would preserve information, but would lie + // 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. - // add: Params extends BBinary - // ? B extends number ? B : never - // : never - // + // 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. - unaryMinus: Signature - subtract: Signature - multiply: Signature - divide: Signature + + 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 @@ -29,16 +47,23 @@ declare module "./type" { } export const add: ImpType<'add', [number, number]> = (a, b) => a + b +export const addReal = add 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: ImpType<'conservativeSqrt', [number]> = + a => isNaN(a) ? NaN : Math.sqrt(a) + export const sqrt = (dep: configDependency & Dependency<'complex', [number, number]>): ImpType<'sqrt', [number]> => { - if (dep.config.predictable || !dep.complex) { - return a => isNaN(a) ? NaN : Math.sqrt(a) - } + if (dep.config.predictable || !dep.complex) return conservativeSqrt return a => { if (isNaN(a)) return NaN if (a >= 0) return Math.sqrt(a) diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts new file mode 100644 index 0000000..b8cc4c5 --- /dev/null +++ b/src/numbers/predicate.ts @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..cd0a418 --- /dev/null +++ b/src/numbers/relational.ts @@ -0,0 +1,27 @@ +import {configDependency} from '../core/Config.js' +import {Signature, ImpType} from '../core/Dispatcher.js' + +const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 + +declare module "./type" { + interface NumbersReturn { + equal: Signature + } +} + +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 + } diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 105ca46..46971fc 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,5 @@ import {ImpType} from '../core/Dispatcher.js' +import type {UnderlyingReal} from '../Complex/type.js' export const number_type = { before: ['Complex'], @@ -25,6 +26,19 @@ export interface NumbersReturn { // 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 From 8c06c8f36ea0216892868864da4ebf91c6dc96c7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 22 Dec 2022 16:12:36 +0000 Subject: [PATCH 4/7] feat: Add generic operation `square` and numeric `unequal` (#4) Co-authored-by: Jos de Jong Co-authored-by: Glen Whitney Reviewed-on: https://code.studioinfinity.org/glen/typocomath/pulls/4 --- src/Complex/type.ts | 13 +++++++++++++ src/all.ts | 1 + src/generic/all.ts | 10 ++++++++++ src/generic/arithmetic.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/generic/type.ts | 3 +++ src/numbers/relational.ts | 29 ++++++++++++++++++----------- 6 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 src/generic/all.ts create mode 100644 src/generic/arithmetic.ts create mode 100644 src/generic/type.ts diff --git a/src/Complex/type.ts b/src/Complex/type.ts index a150700..36c040a 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -30,6 +30,19 @@ export interface ComplexReturn { : Params extends BBinary ? Complex // binary case : never + // 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 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/generic/all.ts b/src/generic/all.ts new file mode 100644 index 0000000..1a008ac --- /dev/null +++ b/src/generic/all.ts @@ -0,0 +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 new file mode 100644 index 0000000..46d6922 --- /dev/null +++ b/src/generic/arithmetic.ts @@ -0,0 +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: 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/relational.ts b/src/numbers/relational.ts index cd0a418..51d7e07 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,27 +1,34 @@ import {configDependency} from '../core/Config.js' -import {Signature, ImpType} from '../core/Dispatcher.js' +import {Signature, ImpType, Dependency} from '../core/Dispatcher.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 declare module "./type" { interface NumbersReturn { equal: Signature + unequal: Signature } } 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 + (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 + 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) } From cbd171922780b18ad808edb1bdccd76f6718373e Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 11:27:39 +0100 Subject: [PATCH 5/7] experiment: convert all implementations to plain types --- src/Complex/all.ts | 7 -- src/Complex/arithmetic.ts | 212 ++++++++++++++++++-------------------- src/Complex/predicate.ts | 25 ++--- src/Complex/relational.ts | 18 +--- src/Complex/type.ts | 97 ++++++----------- src/core/Dispatcher.ts | 24 ----- src/generic/all.ts | 7 -- src/generic/arithmetic.ts | 42 +------- src/generic/type.ts | 3 - src/numbers/all.ts | 7 -- src/numbers/arithmetic.ts | 90 +++++----------- src/numbers/predicate.ts | 13 +-- src/numbers/relational.ts | 44 ++++---- src/numbers/type.ts | 44 +------- 14 files changed, 198 insertions(+), 435 deletions(-) delete mode 100644 src/generic/type.ts diff --git a/src/Complex/all.ts b/src/Complex/all.ts index f5369ec..b0ec237 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,10 +1,3 @@ -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 8fc32fb..941a654 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,139 +1,127 @@ -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 - } -} +import { Complex, complex_binary } from './type.js' export const add = - (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)) + (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)) export const addReal = - (dep: Dependency<'addReal', [T, UnderlyingReal]>): - ImpType<'addReal', [Complex, UnderlyingReal]> => - (z, r) => complex_binary(dep.addReal(z.re, r), z.im) + (dep: { + addReal: (a: T, b: T) => T + }) => + (z: Complex, r: T): Complex => + complex_binary(dep.addReal(z.re, r), z.im) export const unaryMinus = - (dep: Dependency<'unaryMinus', [T]>): - ImpType<'unaryMinus', [Complex]> => - z => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + (dep: { + unaryMinus: (z: T) => T + }) => + (z: Complex): Complex => + complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) export const conj = - (dep: Dependency<'unaryMinus'|'conj', [T]>): - ImpType<'conj', [Complex]> => - z => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) + (dep: { + unaryMinus: (z: T) => T, + conj: (z: T) => T + }) => + (z: Complex): Complex => + complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) export const subtract = - (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)) + (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)) export const multiply = - (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) - } + (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) + } export const absquare = - (dep: Dependency<'absquare', [T]> - & Dependency<'add', BBinary>>): - ImpType<'absquare', [Complex]> => - z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + (dep: { + add: (a: T, b: T) => T, + absquare: (z: T) => T + }) => + (z: Complex): T => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = - (dep: Dependency<'divideByReal', [T, UnderlyingReal]>): - ImpType<'divideByReal', [Complex, UnderlyingReal]> => - (z, r) => complex_binary( - dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + (dep: { + divideByReal: (a: T, b: T) => T + }) => + (z: Complex, r: T) => + complex_binary(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) export const reciprocal = - (dep: Dependency<'conj', [Complex]> - & Dependency<'absquare', [Complex]> - & Dependency<'divideByReal', [Complex, UnderlyingReal]>): - ImpType<'reciprocal', [Complex]> => - z => dep.divideByReal(dep.conj(z), dep.absquare(z)) + (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)) export const divide = - (dep: Dependency<'multiply', [Complex, Complex]> - & Dependency<'reciprocal', [Complex]>): - ImpType<'divide', [Complex, Complex]> => - (w, z) => dep.multiply(w, dep.reciprocal(z)) + (dep: { + multiply: (a: Complex, b: Complex) => Complex, + reciprocal: (z: Complex) => Complex, + }) => + (w: Complex, z: Complex) => dep.multiply(w, dep.reciprocal(z)) export const complexSqrt = - (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)) + (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)) } - // 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: 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) - } + (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) + } export const conservativeSqrt = sqrt diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index eafc5ad..47365fb 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,19 +1,12 @@ -import {Complex} from './type.js' -import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' - -declare module "./type" { - interface ComplexReturn { - isReal: Signature], boolean> - isSquare: Signature], boolean> - } -} +import { Complex } from './type.js' export const isReal = - (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)) + (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)) -export const isSquare: ImpType<'isSquare', [Complex]> = - z => true // FIXME: not correct for Complex once we get there +export const isSquare = + (z: Complex) => true // FIXME: not correct for Complex once we get there diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts index 2a57dc4..2621972 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,15 +1,7 @@ -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 - } -} +import { Complex } from './type.js' export const equal = - (dep: Dependency<'equal', [T,T]>): - ImpType<'equal', [Complex, Complex]> => - (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) + (dep: { + equal: (a: T, b: T) => boolean + }) => + (w: Complex, z: Complex): boolean => 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 36c040a..d0845c8 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,87 +1,54 @@ import { - joinTypes, typeOfDependency, Dependency, BBinary, ImpType, ImpReturns + joinTypes, typeOfDependency, Dependency, } from '../core/Dispatcher.js' -export type Complex = {re: T; im: T;} - -export type UnderlyingReal = - T extends Complex ? UnderlyingReal : 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' && '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 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 - - // 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}) + (dep: { + zero: (z: T) => Complex + }) => + (t: T) => ({ re: t, im: dep.zero(t) }) + +export const complex_binary = + (t: T, u: T): Complex => ({ re: t, im: u }) export const zero = - (dep: Dependency<'zero', [T]>): ImpType<'zero', [Complex]> => - z => complex_binary(dep.zero(z.re), dep.zero(z.im)) + (dep: { + zero: (z: T) => T + }) => + (z: Complex): Complex => complex_binary(dep.zero(z.re), dep.zero(z.im)) export const one = - (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)) + (dep: { + zero: (z: T) => T, + one: (z: T) => T + }) => + (z: Complex): Complex => complex_binary(dep.one(z.re), dep.zero(z.im)) export const nan = - (dep: Dependency<'nan', [T]>): ImpType<'nan', [Complex]> => - z => complex_binary(dep.nan(z.re), dep.nan(z.im)) + (dep: { + nan: (z: T) => T + }) => + (z: Complex): Complex => complex_binary(dep.nan(z.re), dep.nan(z.im)) export const re = - (dep: Dependency<'re', [T]>): ImpType<'re', [Complex]> => - z => dep.re(z.re) + (dep: { + re: (z: T) => T + }) => + (z: Complex): T => dep.re(z.re) diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index aa4c6a9..9b005d9 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -66,30 +66,6 @@ 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 1a008ac..1b1b8a4 100644 --- a/src/generic/all.ts +++ b/src/generic/all.ts @@ -1,10 +1,3 @@ -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 46d6922..99a7aab 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,39 +1,5 @@ -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: Dependency<'multiply', [T, T]>): - ImpType<'square', [T]> => - z => dep.multiply(z, z) + (dep: { + multiply: (x: T, y: T) => T + }) => + (z: T): T => dep.multiply(z, z) diff --git a/src/generic/type.ts b/src/generic/type.ts deleted file mode 100644 index 8589417..0000000 --- a/src/generic/type.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface GenericReturn { - -} \ No newline at end of file diff --git a/src/numbers/all.ts b/src/numbers/all.ts index b034f25..deb4a8e 100644 --- a/src/numbers/all.ts +++ b/src/numbers/all.ts @@ -1,10 +1,3 @@ -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 b02b09f..499a942 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,72 +1,28 @@ -import {configDependency} from '../core/Config.js' -import { - Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType -} from '../core/Dispatcher.js' -import type {Complex, UnderlyingReal} from '../Complex/type.js' +import { Config } from '../core/Config.js' +import type { Complex } from '../Complex/type.js' -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 add = (a: number, b: number): number => a + b export const addReal = add -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 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 conservativeSqrt: ImpType<'conservativeSqrt', [number]> = - a => isNaN(a) ? NaN : Math.sqrt(a) +export const conservativeSqrt = (a: number): number => isNaN(a) ? NaN : Math.sqrt(a) export const sqrt = - (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))) - } - } + (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))) + } + } diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index b8cc4c5..483b103 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,11 +1,2 @@ -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 +export const isReal = (a: number) : boolean => true +export const isSquare = (a: number) : boolean => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index 51d7e07..dac949a 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,34 +1,26 @@ -import {configDependency} from '../core/Config.js' -import {Signature, ImpType, Dependency} from '../core/Dispatcher.js' +import { Config } from '../core/Config.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 -declare module "./type" { - interface NumbersReturn { - equal: Signature - unequal: Signature - } -} - 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 + (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 - 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 + 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 unequal = (dep: Dependency<'equal', [number, number]>): - ImpType<'unequal', [number, number]> => - (x, y) => { - return !dep.equal(x, y) + return false } + +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 46971fc..46fbf6f 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,44 +1,10 @@ -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 => +s} + from: { string: (s: string) => +s } } - -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 +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 From 04024a2a8df83dea08d7293686b71fcafc0659eb Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 12:22:41 +0100 Subject: [PATCH 6/7] fix a TS issue --- src/Complex/type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index d0845c8..50a231f 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -7,7 +7,7 @@ export type Complex = { re: T; im: T; } export const Complex_type = { test: (dep: { testT: (z: unknown) => z is T }) => (z: unknown): z is Complex => - typeof z === 'object' && 're' in z && 'im' in z + 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 40c7cd36af789158b9fd7687efe17c0a2bd68fb9 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 17:16:21 +0100 Subject: [PATCH 7/7] fix and test absquare for quaternion --- .gitignore | 3 +++ src/Complex/arithmetic.ts | 8 ++++---- src/index.ts | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 0dc139f..4788ab4 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/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 941a654..1919d81 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -51,11 +51,11 @@ export const multiply = } export const absquare = - (dep: { - add: (a: T, b: T) => T, - absquare: (z: T) => T + (dep: { + add: (a: U, b: U) => U, + absquare: (z: T) => U }) => - (z: Complex): T => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + (z: Complex): U => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = (dep: { diff --git a/src/index.ts b/src/index.ts index bb83486..7508578 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,22 @@ import {Dispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' export default new Dispatcher(Specifications) + + +// Test https://github.com/josdejong/pocomath/issues/1#issuecomment-1364056151 + +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)