From 1eb73be2fa2a89d2aa7aa26bf146047e566253b5 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 21 Dec 2022 00:18:42 -0500 Subject: [PATCH 01/20] 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 -- 2.34.1 From d55776655f3bbe64c909bb60c5cfe016f44a5c7d Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 21 Dec 2022 11:41:25 -0500 Subject: [PATCH 02/20] 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 -- 2.34.1 From fbec410c4252b82ad9de63a8a4bb3e48cd216f16 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 22 Dec 2022 00:14:58 -0500 Subject: [PATCH 03/20] 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 -- 2.34.1 From 8c06c8f36ea0216892868864da4ebf91c6dc96c7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 22 Dec 2022 16:12:36 +0000 Subject: [PATCH 04/20] 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) } -- 2.34.1 From cbd171922780b18ad808edb1bdccd76f6718373e Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 11:27:39 +0100 Subject: [PATCH 05/20] 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 -- 2.34.1 From 04024a2a8df83dea08d7293686b71fcafc0659eb Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 12:22:41 +0100 Subject: [PATCH 06/20] 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)), -- 2.34.1 From 60ce6212b4725c27f57b1328ec25c6746b49b23c Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 13:52:56 +0100 Subject: [PATCH 07/20] convert code to type aliases --- .gitignore | 3 + src/Complex/all.ts | 3 + src/Complex/arithmetic.ts | 126 ++++++++++++++++++----------------- src/Complex/predicate.ts | 14 ++-- src/Complex/relational.ts | 7 +- src/Complex/type.ts | 42 ++++++------ src/generic/arithmetic.ts | 8 ++- src/interfaces/arithmetic.ts | 28 ++++++++ src/interfaces/relational.ts | 3 + src/numbers/arithmetic.ts | 25 +++---- src/numbers/predicate.ts | 6 +- src/numbers/relational.ts | 9 +-- src/numbers/type.ts | 10 +-- 13 files changed, 169 insertions(+), 115 deletions(-) create mode 100644 src/interfaces/arithmetic.ts create mode 100644 src/interfaces/relational.ts diff --git a/.gitignore b/.gitignore index 0dc139f..df7c548 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,9 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +# Webstiorm +.idea + # yarn v2 .yarn/cache .yarn/unplugged diff --git a/src/Complex/all.ts b/src/Complex/all.ts index b0ec237..3ff6311 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,3 +1,6 @@ import * as Complex from './native.js' +import * as complex from './arithmetic.js' + +export { complex } export {Complex} diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 941a654..0987779 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,49 +1,54 @@ -import { Complex, complex_binary } from './type.js' +import {Complex, complex_binary, FnComplexUnary} from './type.js' +import type { + FnAbsSquare, + FnAdd, + FnAddReal, + FnConj, FnConservativeSqrt, FnDivide, + FnDivideByReal, FnIsReal, FnIsSquare, + FnMultiply, FnNaN, FnRe, FnReciprocal, FnSqrt, + FnSubtract, + FnUnaryMinus, FnZero +} from '../interfaces/arithmetic' 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)) + add: FnAdd + }): FnAdd> => + (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) + addReal: FnAddReal + }): FnAddReal, T> => + (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)) + unaryMinus: FnUnaryMinus + }): FnUnaryMinus> => + (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)) + unaryMinus: FnUnaryMinus, + conj: FnConj + }) : FnConj> => + (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)) + subtract: FnSubtract + }): FnSubtract> => + (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 + add: FnAdd, + subtract: FnSubtract, + multiply: FnMultiply, + conj: FnConj }) => - (w: Complex, z: 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)) @@ -52,44 +57,42 @@ export const multiply = 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)) + add: FnAdd, + absquare: FnAbsSquare + }): FnAbsSquare, T> => + (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)) + divideByReal: FnDivideByReal + }): FnDivideByReal, T> => + (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)) + conj: FnConj>, + absquare: FnAbsSquare, T>, + divideByReal: FnDivideByReal, T> + }): FnReciprocal> => + (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)) + multiply: FnMultiply>, + reciprocal: FnReciprocal>, + }): FnDivide> => + (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 + conservativeSqrt: FnConservativeSqrt, + isSquare: FnIsSquare, + complex: FnComplexUnary, + unaryMinus: FnUnaryMinus, + zero: FnZero, + nan: FnNaN> }) => - (r: T): Complex => { + (r) => { if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) const negative = dep.unaryMinus(r) if (dep.isSquare(negative)) { @@ -104,17 +107,16 @@ export const complexSqrt = 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, - + isReal: FnIsReal>, + complexSqrt: FnSqrt, + conservativeSqrt: FnConservativeSqrt, + absquare: FnAbsSquare, T>, + addReal: FnAddReal, T>, + divideByReal: FnDivideByReal, T>, + add: FnAdd, + re: FnRe, T>, }) => - (z: Complex) => { + (z: Complex): Complex | T => { if (dep.isReal(z)) return dep.complexSqrt(z.re) const myabs = dep.conservativeSqrt(dep.absquare(z)) const num = dep.addReal(z, myabs) diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index 47365fb..557ea83 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,12 +1,14 @@ import { Complex } from './type.js' +import {FnEqual} from '../interfaces/relational' +import {FnAdd, FnIsReal, FnIsSquare} from '../interfaces/arithmetic' 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)) + equal: FnEqual, + add: FnAdd, + isReal: FnIsReal + }): FnIsReal> => + (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 + (): FnIsSquare> => (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..9807d71 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,7 +1,8 @@ import { Complex } from './type.js' +import {FnEqual} from '../interfaces/relational' 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) + equal: FnEqual + }): FnEqual> => + (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..3b4e82d 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,6 +1,7 @@ import { joinTypes, typeOfDependency, Dependency, } from '../core/Dispatcher.js' +import type {FnNaN, FnOne, FnRe, FnZero} from '../interfaces/arithmetic.js' export type Complex = { re: T; im: T; } @@ -8,7 +9,7 @@ export const Complex_type = { 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), + && dep.testT(z['re']) && dep.testT(z['im']), infer: (dep: typeOfDependency) => (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), from: { @@ -19,36 +20,39 @@ export const Complex_type = { } } +export type FnComplexUnary = (t: T) => Complex + export const complex_unary = (dep: { - zero: (z: T) => Complex - }) => - (t: T) => ({ re: t, im: dep.zero(t) }) + zero: FnZero + }): FnComplexUnary => + (t) => ({ re: t, im: dep.zero(t) }) -export const complex_binary = - (t: T, u: T): Complex => ({ re: t, im: u }) +export type FnComplexBinary = (re: T, im: T) => Complex + +export const complex_binary = (t: T, u: T): Complex => ({ 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)) + zero: FnZero + }): FnZero> => + (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)) + zero: FnZero, + one: FnOne + }): FnOne> => + (z) => 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)) + nan: FnNaN + }): FnNaN> => + (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) + re: FnRe + }): FnRe, T> => + (z) => dep.re(z.re) diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index 99a7aab..a1379df 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,5 +1,7 @@ +import type { FnMultiply, FnSquare } from "../interfaces/arithmetic" + export const square = (dep: { - multiply: (x: T, y: T) => T - }) => - (z: T): T => dep.multiply(z, z) + multiply: FnMultiply + }): FnSquare => + (z) => dep.multiply(z, z) diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts new file mode 100644 index 0000000..91b59bf --- /dev/null +++ b/src/interfaces/arithmetic.ts @@ -0,0 +1,28 @@ +// shared interfaces + +import { Complex } from "../Complex/type" + +// Note: right now I've added an 'Fn*' prefix, +// so it is clear that the type hold a function type definition +// This is not necessary though, it is just a naming convention. +export type FnAdd = (a: T, b: T) => T +export type FnAddReal = (a: T, b: U) => T +export type FnUnaryMinus = (a: T) => T +export type FnConj = (a: T) => T +export type FnSubtract = (a: T, b: T) => T +export type FnMultiply = (a: T, b: T) => T +export type FnAbsSquare = (a: T) => U +export type FnReciprocal = (a: T) => T +export type FnDivide = (a: T, b: T) => T +export type FnDivideByReal = (a: T, b: U) => T +export type FnConservativeSqrt = (a: T) => T +export type FnSqrt = (a: T) => T | Complex +export type FnSquare = (z: T) => T + +export type FnIsReal = (a: T) => boolean +export type FnIsSquare = (a: T) => boolean + +export type FnZero = (a: T) => T +export type FnOne = (a: T) => T +export type FnNaN = (a: T) => T +export type FnRe = (a: T) => U diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts new file mode 100644 index 0000000..2865f77 --- /dev/null +++ b/src/interfaces/relational.ts @@ -0,0 +1,3 @@ + +export type FnEqual = (a: T, b: T) => boolean +export type FnUnequal = (a: T, b: T) => boolean diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 499a942..7142797 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,24 +1,25 @@ import { Config } from '../core/Config.js' -import type { Complex } from '../Complex/type.js' +import type { FnComplexBinary } from '../Complex/type.js' +import { FnAdd, FnConj, FnSubtract, FnUnaryMinus, FnMultiply, FnAbsSquare, FnReciprocal, FnDivide, FnConservativeSqrt, FnSqrt } from '../interfaces/arithmetic.js' -export const add = (a: number, b: number): number => a + b +export const add: FnAdd = (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 unaryMinus: FnUnaryMinus = (a) => -a +export const conj: FnConj = (a) => a +export const subtract: FnSubtract = (a, b) => a - b +export const multiply: FnMultiply = (a, b) => a * b +export const absquare: FnAbsSquare = (a) => a * a +export const reciprocal: FnReciprocal = (a) => 1 / a +export const divide: FnDivide = (a, b) => a / b export const divideByReal = divide -export const conservativeSqrt = (a: number): number => isNaN(a) ? NaN : Math.sqrt(a) +export const conservativeSqrt: FnConservativeSqrt = (a) => isNaN(a) ? NaN : Math.sqrt(a) export const sqrt = (dep: { config: Config, - complex: (re: number, im: number) => Complex - }): (a: number) => number | Complex => { + complex: FnComplexBinary + }): FnSqrt => { if (dep.config.predictable || !dep.complex) return conservativeSqrt return a => { if (isNaN(a)) return NaN diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index 483b103..4015c55 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,2 +1,4 @@ -export const isReal = (a: number) : boolean => true -export const isSquare = (a: number) : boolean => a >= 0 +import type { FnIsReal, FnIsSquare } from "../interfaces/arithmetic" + +export const isReal: FnIsReal = (a) => true +export const isSquare: FnIsSquare = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index dac949a..ae9a63d 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,11 +1,12 @@ import { Config } from '../core/Config.js' +import type { FnEqual, FnUnequal } from '../interfaces/relational.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 export const equal = (dep: { config: Config - }) => (x: number, y: number): boolean => { + }): FnEqual => (x, y) => { const eps = dep.config.epsilon if (eps === null || eps === undefined) return x === y if (x === y) return true @@ -21,6 +22,6 @@ export const equal = } export const unequal = (dep: { - equal: (x: number, y: number) => boolean -}) => - (x: number, y: number): boolean => !dep.equal(x, y) + equal: FnEqual +}): FnUnequal => + (x, y) => !dep.equal(x, y) diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 46fbf6f..77336ef 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,10 +1,12 @@ +import type { FnNaN, FnOne, FnZero, FnRe } from "../interfaces/arithmetic" + export const number_type = { before: ['Complex'], test: (n: unknown): n is number => typeof n === 'number', from: { string: (s: string) => +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 const zero: FnZero = (a) => 0 +export const one: FnOne = (a) => 1 +export const nan: FnNaN = (a) => NaN +export const re: FnRe = (a) => a -- 2.34.1 From a5848125e45f208b6a6a3f435e81f7ea15407f03 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 23 Dec 2022 17:18:24 +0100 Subject: [PATCH 08/20] fix and test absquare for quaternion --- src/Complex/arithmetic.ts | 8 ++++---- src/index.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 0987779..a9d0d42 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -56,10 +56,10 @@ export const multiply = } export const absquare = - (dep: { - add: FnAdd, - absquare: FnAbsSquare - }): FnAbsSquare, T> => + (dep: { + add: FnAdd, + absquare: FnAbsSquare + }): FnAbsSquare, U> => (z) => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = diff --git a/src/index.ts b/src/index.ts index bb83486..297b271 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,19 @@ import {Dispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' export default new Dispatcher(Specifications) + +import {Complex} from './Complex/type.js' +import {absquare as absquare_complex} from './Complex/arithmetic.js' + +const mockRealAdd = (a: number, b: number) => a+b +const mockComplexAbsquare = (z: Complex) => z.re*z.re + z.im*z.im + +const quatAbsquare = absquare_complex({ + add: mockRealAdd, + absquare: mockComplexAbsquare +}) + +const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) +const typeTest: typeof myabs = 7 // check myabs is just a number + +console.log('Result is', myabs) -- 2.34.1 From 74e2aef524634c0b56dbfc6fd266975a9999013c Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 24 Dec 2022 00:41:35 -0500 Subject: [PATCH 09/20] refactor: tighter universal interface types --- src/Complex/arithmetic.ts | 193 +++++++++++++++++------------------ src/Complex/predicate.ts | 20 ++-- src/Complex/relational.ts | 11 +- src/Complex/type.ts | 58 ++++++----- src/generic/all.ts | 4 +- src/generic/arithmetic.ts | 9 +- src/generic/native.ts | 2 + src/generic/relational.ts | 5 + src/interfaces/arithmetic.ts | 47 ++++----- src/interfaces/predicate.ts | 2 + src/interfaces/relational.ts | 5 +- src/interfaces/type.ts | 32 ++++++ src/numbers/arithmetic.ts | 36 ++++--- src/numbers/predicate.ts | 6 +- src/numbers/relational.ts | 11 +- src/numbers/type.ts | 24 ++++- 16 files changed, 251 insertions(+), 214 deletions(-) create mode 100644 src/generic/native.ts create mode 100644 src/generic/relational.ts create mode 100644 src/interfaces/predicate.ts create mode 100644 src/interfaces/type.ts diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index a9d0d42..de0e16d 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,129 +1,120 @@ -import {Complex, complex_binary, FnComplexUnary} from './type.js' +import {Complex, ComplexOp} from './type.js' import type { - FnAbsSquare, - FnAdd, - FnAddReal, - FnConj, FnConservativeSqrt, FnDivide, - FnDivideByReal, FnIsReal, FnIsSquare, - FnMultiply, FnNaN, FnRe, FnReciprocal, FnSqrt, - FnSubtract, - FnUnaryMinus, FnZero -} from '../interfaces/arithmetic' + AbsquareOp, AddOp, AddRealOp, ConjOp, ConservativeSqrtOp, DivideOp, + DivideByRealOp, MultiplyOp, ReciprocalOp, SqrtOp, SubtractOp, + UnaryMinusOp +} from '../interfaces/arithmetic.js' +import type { + NanOp, ReOp, ZeroOp, Depends, RealType, WithConstants, NaNType +} from '../interfaces/type.js' +import type {IsSquareOp, IsRealOp} from '../interfaces/predicate.js' export const add = - (dep: { - add: FnAdd - }): FnAdd> => - (w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im)) + (dep: Depends> & Depends>): AddOp> => + (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) export const addReal = - (dep: { - addReal: FnAddReal - }): FnAddReal, T> => - (z, r) => complex_binary(dep.addReal(z.re, r), z.im) + (dep: Depends> & Depends>): + AddRealOp> => + (z, r) => dep.complex(dep.addReal(z.re, r), z.im) export const unaryMinus = - (dep: { - unaryMinus: FnUnaryMinus - }): FnUnaryMinus> => - (z) => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + (dep: Depends> & Depends>): + UnaryMinusOp> => + z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) export const conj = - (dep: { - unaryMinus: FnUnaryMinus, - conj: FnConj - }) : FnConj> => - (z) => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) + (dep: Depends> + & Depends> + & Depends>): + ConjOp> => + z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im)) export const subtract = - (dep: { - subtract: FnSubtract - }): FnSubtract> => - (w, z) => complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) + (dep: Depends> & Depends>): + SubtractOp> => + (w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) export const multiply = - (dep: { - add: FnAdd, - subtract: FnSubtract, - multiply: FnMultiply, - conj: FnConj - }) => - (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: Depends> + & Depends> + & Depends> + & Depends> + & Depends>): + MultiplyOp> => + (w, z) => { + const mult = dep.multiply + const realpart = dep.subtract( + mult( w.re, z.re), mult(dep.conj(w.im), z.im)) + const imagpart = dep.add( + mult(dep.conj(w.re), z.im), mult( w.im, z.re)) + return dep.complex(realpart, imagpart) + } export const absquare = - (dep: { - add: FnAdd, - absquare: FnAbsSquare - }): FnAbsSquare, U> => - (z) => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + (dep: Depends>> & Depends>): + AbsquareOp> => + z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = - (dep: { - divideByReal: FnDivideByReal - }): FnDivideByReal, T> => - (z, r) => complex_binary(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + (dep: Depends> & Depends>): + DivideByRealOp> => + (z, r) => dep.complex(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) export const reciprocal = - (dep: { - conj: FnConj>, - absquare: FnAbsSquare, T>, - divideByReal: FnDivideByReal, T> - }): FnReciprocal> => - (z) => dep.divideByReal(dep.conj(z), dep.absquare(z)) + (dep: Depends>> + & Depends>> + & Depends>>): + ReciprocalOp> => + z => dep.divideByReal(dep.conj(z), dep.absquare(z)) export const divide = - (dep: { - multiply: FnMultiply>, - reciprocal: FnReciprocal>, - }): FnDivide> => - (w, z) => dep.multiply(w, dep.reciprocal(z)) + (dep: Depends>> + & Depends>>): + DivideOp> => + (w, z) => dep.multiply(w, dep.reciprocal(z)) +export type ComplexSqrtOp = { + op?: 'complexSqrt', + (a: T): Complex | NaNType>> +} +// Complex square root of a real type T export const complexSqrt = - (dep: { - conservativeSqrt: FnConservativeSqrt, - isSquare: FnIsSquare, - complex: FnComplexUnary, - unaryMinus: FnUnaryMinus, - zero: FnZero, - nan: FnNaN> - }) => - (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)) + (dep: Depends> + & Depends> + & Depends> + & Depends>> + & Depends> + & Depends>>>): ComplexSqrtOp => + r => { + if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) + const negative = dep.unaryMinus(r) + if (dep.isSquare(negative)) { + return dep.complex(dep.zero(r), dep.conservativeSqrt(negative)) } + // neither the real number or its negative is a square; could happen + // for example with bigint. So there is no square root. So we have to + // return the NaN of the type. + return dep.nan(dep.complex(r)) + } export const sqrt = - (dep: { - isReal: FnIsReal>, - complexSqrt: FnSqrt, - conservativeSqrt: FnConservativeSqrt, - absquare: FnAbsSquare, T>, - addReal: FnAddReal, T>, - divideByReal: FnDivideByReal, T>, - add: FnAdd, - re: FnRe, T>, - }) => - (z: Complex): Complex | T => { - 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: Depends>> + & Depends> + & Depends>> + & Depends>> + & Depends>> + & Depends>> + & Depends>> + & Depends>>): SqrtOp> => + 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 557ea83..cddd383 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,14 +1,12 @@ -import { Complex } from './type.js' -import {FnEqual} from '../interfaces/relational' -import {FnAdd, FnIsReal, FnIsSquare} from '../interfaces/arithmetic' +import {Complex} from './type.js' +import {EqualOp} from '../interfaces/relational.js' +import {AddOp} from '../interfaces/arithmetic.js' +import type {Depends} from '../interfaces/type.js' +import type {IsRealOp, IsSquareOp} from '../interfaces/predicate.js' export const isReal = - (dep: { - equal: FnEqual, - add: FnAdd, - isReal: FnIsReal - }): FnIsReal> => - (z) => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) + (dep: Depends> & Depends> & Depends>): + IsRealOp> => + z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) -export const isSquare = - (): FnIsSquare> => (z) => true // FIXME: not correct for Complex once we get there +export const isSquare: IsSquareOp> = z => true // FIXME: not correct for Complex once we get there diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts index 9807d71..89db758 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,8 +1,7 @@ -import { Complex } from './type.js' -import {FnEqual} from '../interfaces/relational' +import {Complex} from './type.js' +import {Depends} from '../interfaces/type.js' +import {EqualOp} from '../interfaces/relational.js' export const equal = - (dep: { - equal: FnEqual - }): FnEqual> => - (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) + (dep: Depends>): EqualOp> => + (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 3b4e82d..f995f92 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,7 +1,9 @@ import { joinTypes, typeOfDependency, Dependency, } from '../core/Dispatcher.js' -import type {FnNaN, FnOne, FnRe, FnZero} from '../interfaces/arithmetic.js' +import type { + OneOp, ZeroOp, NanOp, ReOp, ZeroType, OneType, NaNType, Depends +} from '../interfaces/type.js' export type Complex = { re: T; im: T; } @@ -20,39 +22,41 @@ export const Complex_type = { } } -export type FnComplexUnary = (t: T) => Complex +declare module "../interfaces/type" { + interface AssociatedTypes { + Complex: T extends Complex ? { + type: Complex + zero: Complex> + one: Complex | ZeroType> + nan: Complex> + real: RealType + } : never + } +} -export const complex_unary = - (dep: { - zero: FnZero - }): FnComplexUnary => - (t) => ({ re: t, im: dep.zero(t) }) +export type ComplexOp = {op?: 'complex', (a: T, b?: T): Complex} -export type FnComplexBinary = (re: T, im: T) => Complex - -export const complex_binary = (t: T, u: T): Complex => ({ re: t, im: u }) +export const complex = + (dep: Depends>): ComplexOp> => + (a, b) => ({re: a, im: b || dep.zero(a)}) export const zero = - (dep: { - zero: FnZero - }): FnZero> => - (z) => complex_binary(dep.zero(z.re), dep.zero(z.im)) + (dep: Depends> & Depends>>): + ZeroOp> => + z => dep.complex(dep.zero(z.re), dep.zero(z.im)) export const one = - (dep: { - zero: FnZero, - one: FnOne - }): FnOne> => - (z) => complex_binary(dep.one(z.re), dep.zero(z.im)) + (dep: Depends> + & Depends> + & Depends|OneType>>): + OneOp> => + z => dep.complex(dep.one(z.re), dep.zero(z.im)) export const nan = - (dep: { - nan: FnNaN - }): FnNaN> => - (z) => complex_binary(dep.nan(z.re), dep.nan(z.im)) + (dep: Depends> & Depends>>): + NanOp> => + z => dep.complex(dep.nan(z.re), dep.nan(z.im)) export const re = - (dep: { - re: FnRe - }): FnRe, T> => - (z) => dep.re(z.re) + (dep: Depends>): ReOp> => + z => dep.re(z.re) diff --git a/src/generic/all.ts b/src/generic/all.ts index 1b1b8a4..78fa222 100644 --- a/src/generic/all.ts +++ b/src/generic/all.ts @@ -1,3 +1 @@ -import * as generic from './arithmetic.js' - -export { generic } +export * as generic from './native.js' diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index a1379df..a7cf2b7 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,7 +1,6 @@ -import type { FnMultiply, FnSquare } from "../interfaces/arithmetic" +import type {Depends} from '../interfaces/type.js' +import type {MultiplyOp, SquareOp} from '../interfaces/arithmetic.js' export const square = - (dep: { - multiply: FnMultiply - }): FnSquare => - (z) => dep.multiply(z, z) + (dep: Depends>): SquareOp => + z => dep.multiply(z, z) diff --git a/src/generic/native.ts b/src/generic/native.ts new file mode 100644 index 0000000..b8290ae --- /dev/null +++ b/src/generic/native.ts @@ -0,0 +1,2 @@ +export * from './arithmetic.js' +export * from './relational.js' diff --git a/src/generic/relational.ts b/src/generic/relational.ts new file mode 100644 index 0000000..f47c392 --- /dev/null +++ b/src/generic/relational.ts @@ -0,0 +1,5 @@ +import {Depends} from '../interfaces/type.js' +import type {EqualOp, UnequalOp} from '../interfaces/relational.js' + +export const unequal = (dep: Depends>): UnequalOp => + (x, y) => !dep.equal(x, y) diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts index 91b59bf..80e1155 100644 --- a/src/interfaces/arithmetic.ts +++ b/src/interfaces/arithmetic.ts @@ -1,28 +1,25 @@ -// shared interfaces +import type {Complex} from '../Complex/type.js' +import type {RealType, WithConstants, NaNType} from './type.js' -import { Complex } from "../Complex/type" - -// Note: right now I've added an 'Fn*' prefix, -// so it is clear that the type hold a function type definition +// Note: right now I've added an 'Op' suddix, +// so it is clear that the type holds the function type of an operation // This is not necessary though, it is just a naming convention. -export type FnAdd = (a: T, b: T) => T -export type FnAddReal = (a: T, b: U) => T -export type FnUnaryMinus = (a: T) => T -export type FnConj = (a: T) => T -export type FnSubtract = (a: T, b: T) => T -export type FnMultiply = (a: T, b: T) => T -export type FnAbsSquare = (a: T) => U -export type FnReciprocal = (a: T) => T -export type FnDivide = (a: T, b: T) => T -export type FnDivideByReal = (a: T, b: U) => T -export type FnConservativeSqrt = (a: T) => T -export type FnSqrt = (a: T) => T | Complex -export type FnSquare = (z: T) => T +export type AddOp = {op?: 'add', (a: T, b: T): T} +export type AddRealOp = {op?: 'addReal', (a: T, b: RealType): T} +export type UnaryMinusOp = {op?: 'unaryMinus', (a: T): T} +export type ConjOp = {op?: 'conj', (a: T): T} +export type SubtractOp = {op?: 'subtract', (a: T, b: T): T} +export type MultiplyOp = {op?: 'multiply', (a: T, b: T): T} +export type AbsquareOp = {op?: 'absquare', (a: T): RealType} +export type ReciprocalOp = {op?: 'reciprocal', (a: T): T} +export type DivideOp = {op?: 'divide', (a: T, b: T): T} +export type DivideByRealOp = {op?: 'divideByReal', (a: T, b: RealType): T} +export type ConservativeSqrtOp = {op?: 'conservativeSqrt', (a: T): T} +export type SqrtOp = { + op?: 'sqrt', + (a: T): T extends Complex + ? Complex | NaNType>> + : T | Complex +} +export type SquareOp = {op?: 'square', (z: T): T} -export type FnIsReal = (a: T) => boolean -export type FnIsSquare = (a: T) => boolean - -export type FnZero = (a: T) => T -export type FnOne = (a: T) => T -export type FnNaN = (a: T) => T -export type FnRe = (a: T) => U diff --git a/src/interfaces/predicate.ts b/src/interfaces/predicate.ts new file mode 100644 index 0000000..37a69ea --- /dev/null +++ b/src/interfaces/predicate.ts @@ -0,0 +1,2 @@ +export type IsRealOp = {op?: 'isReal', (a: T): boolean} +export type IsSquareOp = {op?: 'isSquare', (a: T): boolean} diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts index 2865f77..29529ff 100644 --- a/src/interfaces/relational.ts +++ b/src/interfaces/relational.ts @@ -1,3 +1,2 @@ - -export type FnEqual = (a: T, b: T) => boolean -export type FnUnequal = (a: T, b: T) => boolean +export type EqualOp = {op?: 'equal', (a: T, b: T): boolean} +export type UnequalOp = {op?: 'unequal', (a: T, b: T): boolean} diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts new file mode 100644 index 0000000..1d7e103 --- /dev/null +++ b/src/interfaces/type.ts @@ -0,0 +1,32 @@ +export interface AssociatedTypes { + undefined: { + type: undefined + zero: undefined + one: undefined + nan: undefined + real: undefined + } +} + +type AssociatedTypeNames = keyof AssociatedTypes['undefined'] +export type Lookup = { + [K in keyof AssociatedTypes]: + T extends AssociatedTypes[K]['type'] ? AssociatedTypes[K][Name] : never +}[keyof AssociatedTypes] + +export type ZeroType = Lookup +export type OneType = Lookup +export type WithConstants = T | ZeroType | OneType +export type NaNType = Lookup +export type RealType = Lookup + +export type ZeroOp = {op?: 'zero', (a: WithConstants): ZeroType} +export type OneOp = {op?: 'one', (a: WithConstants): OneType} +export type NanOp = {op?: 'nan', (a: T|NaNType): NaNType} +export type ReOp = {op?: 're', (a: T): RealType} + +type NamedFunction = {op?: string, (...params: any[]): any} +export type Depends = + {[K in FuncType['op']]: FuncType} + + diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 7142797..6a05fe8 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,26 +1,28 @@ -import { Config } from '../core/Config.js' -import type { FnComplexBinary } from '../Complex/type.js' -import { FnAdd, FnConj, FnSubtract, FnUnaryMinus, FnMultiply, FnAbsSquare, FnReciprocal, FnDivide, FnConservativeSqrt, FnSqrt } from '../interfaces/arithmetic.js' +import type {configDependency} from '../core/Config.js' +import type {ComplexOp} from '../Complex/type.js' +import type { + AddOp, ConjOp, SubtractOp, UnaryMinusOp, MultiplyOp, + AbsquareOp, ReciprocalOp, DivideOp, ConservativeSqrtOp, SqrtOp +} from '../interfaces/arithmetic.js' +import type {Depends} from '../interfaces/type.js' -export const add: FnAdd = (a, b) => a + b +export const add: AddOp = (a, b) => a + b export const addReal = add -export const unaryMinus: FnUnaryMinus = (a) => -a -export const conj: FnConj = (a) => a -export const subtract: FnSubtract = (a, b) => a - b -export const multiply: FnMultiply = (a, b) => a * b -export const absquare: FnAbsSquare = (a) => a * a -export const reciprocal: FnReciprocal = (a) => 1 / a -export const divide: FnDivide = (a, b) => a / b +export const unaryMinus: UnaryMinusOp = a => -a +export const conj: ConjOp = a => a +export const subtract: SubtractOp = (a, b) => a - b +export const multiply: MultiplyOp = (a, b) => a * b +export const absquare: AbsquareOp = a => a * a +export const reciprocal: ReciprocalOp = a => 1 / a +export const divide: DivideOp = (a, b) => a / b export const divideByReal = divide -export const conservativeSqrt: FnConservativeSqrt = (a) => isNaN(a) ? NaN : Math.sqrt(a) +const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a) +export const conservativeSqrt: ConservativeSqrtOp = basicSqrt export const sqrt = - (dep: { - config: Config, - complex: FnComplexBinary - }): FnSqrt => { - if (dep.config.predictable || !dep.complex) return conservativeSqrt + (dep: configDependency & Depends>): SqrtOp => { + if (dep.config.predictable || !dep.complex) return basicSqrt 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 index 4015c55..2018e56 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,4 +1,4 @@ -import type { FnIsReal, FnIsSquare } from "../interfaces/arithmetic" +import type { IsRealOp, IsSquareOp } from '../interfaces/predicate.js' -export const isReal: FnIsReal = (a) => true -export const isSquare: FnIsSquare = (a) => a >= 0 +export const isReal: IsRealOp = (a) => true +export const isSquare: IsSquareOp = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index ae9a63d..5dbb3c5 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,12 +1,12 @@ -import { Config } from '../core/Config.js' -import type { FnEqual, FnUnequal } from '../interfaces/relational.js' +import {Config} from '../core/Config.js' +import type {EqualOp} from '../interfaces/relational.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 export const equal = (dep: { config: Config - }): FnEqual => (x, y) => { + }): EqualOp => (x, y) => { const eps = dep.config.epsilon if (eps === null || eps === undefined) return x === y if (x === y) return true @@ -20,8 +20,3 @@ export const equal = return false } - -export const unequal = (dep: { - equal: FnEqual -}): FnUnequal => - (x, y) => !dep.equal(x, y) diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 77336ef..ff1ff61 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,4 @@ -import type { FnNaN, FnOne, FnZero, FnRe } from "../interfaces/arithmetic" +import type { OneOp, ZeroOp, NanOp, ReOp } from '../interfaces/type.js' export const number_type = { before: ['Complex'], @@ -6,7 +6,21 @@ export const number_type = { from: { string: (s: string) => +s } } -export const zero: FnZero = (a) => 0 -export const one: FnOne = (a) => 1 -export const nan: FnNaN = (a) => NaN -export const re: FnRe = (a) => a +declare module "../interfaces/type" { + interface AssociatedTypes { + numbers: { + type: number + zero: 0 + one: 1 + nan: typeof NaN + real: number + } + } +} + +// I don't like the redundancy of repeating 'zero' and 'ZeroOp', any +// way to eliminate that? +export const zero: ZeroOp = (a) => 0 +export const one: OneOp = (a) => 1 +export const nan: NanOp = (a) => NaN +export const re: ReOp = (a) => a -- 2.34.1 From 072b2a1f79e3fb4b4dea92647d368f6e0c5096be Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 24 Dec 2022 10:09:14 -0500 Subject: [PATCH 10/20] refactor: Streamline publishing operations Avoids clumsy naming properties by making the names the keys in an interface to which the signatures of all operations must be published. This also reduces the number of different symbols and avoids long lists of imports in the modules implementing multiple operations, which were redundant with the list of functions exported from such modules. --- src/Complex/arithmetic.ts | 127 ++++++++++++++--------------------- src/Complex/predicate.ts | 11 ++- src/Complex/relational.ts | 5 +- src/Complex/type.ts | 29 ++++---- src/generic/arithmetic.ts | 5 +- src/generic/relational.ts | 6 +- src/interfaces/arithmetic.ts | 42 ++++++------ src/interfaces/predicate.ts | 11 ++- src/interfaces/relational.ts | 11 ++- src/interfaces/type.ts | 58 ++++++++++++---- src/numbers/arithmetic.ts | 30 ++++----- src/numbers/predicate.ts | 6 +- src/numbers/relational.ts | 9 ++- src/numbers/type.ts | 13 ++-- 14 files changed, 189 insertions(+), 174 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index de0e16d..f6089f4 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,47 +1,45 @@ -import {Complex, ComplexOp} from './type.js' +import {Complex} from './type.js' import type { - AbsquareOp, AddOp, AddRealOp, ConjOp, ConservativeSqrtOp, DivideOp, - DivideByRealOp, MultiplyOp, ReciprocalOp, SqrtOp, SubtractOp, - UnaryMinusOp -} from '../interfaces/arithmetic.js' -import type { - NanOp, ReOp, ZeroOp, Depends, RealType, WithConstants, NaNType + Dependencies, OpType, OpReturns, RealType, ZeroType } from '../interfaces/type.js' -import type {IsSquareOp, IsRealOp} from '../interfaces/predicate.js' + +declare module "../interfaces/type" { + interface Operations { + // TODO: Make Dispatcher collapse operations that start with the same + // prefix up to a possible `_` + add_real: {params: [T, RealType], returns: T} + divide_real: {params: [T, RealType], returns: T} + } +} export const add = - (dep: Depends> & Depends>): AddOp> => + (dep: Dependencies<'add' | 'complex', T>): OpType<'add', Complex> => (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) -export const addReal = - (dep: Depends> & Depends>): - AddRealOp> => - (z, r) => dep.complex(dep.addReal(z.re, r), z.im) +export const add_real = + (dep: Dependencies<'add_real' | 'complex', T>): + OpType<'add_real', Complex> => + (z, r) => dep.complex(dep.add_real(z.re, r), z.im) export const unaryMinus = - (dep: Depends> & Depends>): - UnaryMinusOp> => + (dep: Dependencies<'unaryMinus' | 'complex', T>): + OpType<'unaryMinus', Complex> => z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) export const conj = - (dep: Depends> - & Depends> - & Depends>): - ConjOp> => + (dep: Dependencies<'unaryMinus' | 'conj' | 'complex', T>): + OpType<'conj', Complex> => z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im)) export const subtract = - (dep: Depends> & Depends>): - SubtractOp> => + (dep: Dependencies<'subtract' | 'complex', T>): + OpType<'subtract', Complex> => (w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) export const multiply = - (dep: Depends> - & Depends> - & Depends> - & Depends> - & Depends>): - MultiplyOp> => + (dep: Dependencies< + 'add' | 'subtract' | 'multiply' | 'conj' | 'complex', T>): + OpType<'multiply', Complex> => (w, z) => { const mult = dep.multiply const realpart = dep.subtract( @@ -52,69 +50,48 @@ export const multiply = } export const absquare = - (dep: Depends>> & Depends>): - AbsquareOp> => + (dep: Dependencies<'absquare', T> + & Dependencies<'add', OpReturns<'absquare', T>>): + OpType<'absquare', Complex> => z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = - (dep: Depends> & Depends>): - DivideByRealOp> => - (z, r) => dep.complex(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + (dep: Dependencies<'divide_real' | 'complex', T>): + OpType<'divide_real', Complex> => + (z, r) => dep.complex(dep.divide_real(z.re, r), dep.divide_real(z.im, r)) export const reciprocal = - (dep: Depends>> - & Depends>> - & Depends>>): - ReciprocalOp> => - z => dep.divideByReal(dep.conj(z), dep.absquare(z)) + (dep: Dependencies<'conj' | 'absquare' | 'divide_real', Complex>): + OpType<'reciprocal', Complex> => + z => dep.divide_real(dep.conj(z), dep.absquare(z)) export const divide = - (dep: Depends>> - & Depends>>): - DivideOp> => + (dep: Dependencies<'multiply' | 'reciprocal', Complex>): + OpType<'divide', Complex> => (w, z) => dep.multiply(w, dep.reciprocal(z)) -export type ComplexSqrtOp = { - op?: 'complexSqrt', - (a: T): Complex | NaNType>> -} -// Complex square root of a real type T -export const complexSqrt = - (dep: Depends> - & Depends> - & Depends> - & Depends>> - & Depends> - & Depends>>>): ComplexSqrtOp => - r => { - if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) - const negative = dep.unaryMinus(r) - if (dep.isSquare(negative)) { - return dep.complex(dep.zero(r), dep.conservativeSqrt(negative)) - } - // neither the real number or its negative is a square; could happen - // for example with bigint. So there is no square root. So we have to - // return the NaN of the type. - return dep.nan(dep.complex(r)) - } - export const sqrt = - (dep: Depends>> - & Depends> - & Depends>> - & Depends>> - & Depends>> - & Depends>> - & Depends>> - & Depends>>): SqrtOp> => + (dep: + Dependencies< + 'conservativeSqrt' | 'add' | 'unaryMinus' | 'equal', RealType> + & Dependencies<'zero' | 'add_real', T> + & Dependencies<'complex', T | ZeroType> + & Dependencies<'absquare' | 're' | 'divide_real', Complex> + & {add_complex_real: OpType<'add_real', Complex>}): + OpType<'sqrt', Complex> => z => { - if (dep.isReal(z)) return dep.complexSqrt(z.re) const myabs = dep.conservativeSqrt(dep.absquare(z)) - const num = dep.addReal(z, myabs) const r = dep.re(z) + const negr = dep.unaryMinus(r) + if (dep.equal(myabs, negr)) { + // pure imaginary square root; z.im already sero + return dep.complex( + dep.zero(z.re), dep.add_real(z.im, dep.conservativeSqrt(negr))) + } + const num = dep.add_complex_real(z, myabs) const denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) const denom = dep.conservativeSqrt(denomsq) - return dep.divideByReal(num, denom) + return dep.divide_real(num, denom) } export const conservativeSqrt = sqrt diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index cddd383..e6c4c7f 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,12 +1,9 @@ import {Complex} from './type.js' -import {EqualOp} from '../interfaces/relational.js' -import {AddOp} from '../interfaces/arithmetic.js' -import type {Depends} from '../interfaces/type.js' -import type {IsRealOp, IsSquareOp} from '../interfaces/predicate.js' +import type {Dependencies, OpType} from '../interfaces/type.js' export const isReal = - (dep: Depends> & Depends> & Depends>): - IsRealOp> => + (dep: Dependencies<'add' | 'equal' | 'isReal', T>): + OpType<'isReal', Complex> => z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) -export const isSquare: IsSquareOp> = z => true // FIXME: not correct for Complex once we get there +export const isSquare: OpType<'isSquare', Complex> = z => true // FIXME: not correct for Complex once we get there diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts index 89db758..d0b9e52 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,7 +1,6 @@ import {Complex} from './type.js' -import {Depends} from '../interfaces/type.js' -import {EqualOp} from '../interfaces/relational.js' +import {Dependencies, OpType} from '../interfaces/type.js' export const equal = - (dep: Depends>): EqualOp> => + (dep: Dependencies<'equal', T>): OpType<'equal', Complex> => (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index f995f92..8c669ea 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -2,7 +2,7 @@ import { joinTypes, typeOfDependency, Dependency, } from '../core/Dispatcher.js' import type { - OneOp, ZeroOp, NanOp, ReOp, ZeroType, OneType, NaNType, Depends + ZeroType, OneType, NaNType, Dependencies, OpType, OpReturns } from '../interfaces/type.js' export type Complex = { re: T; im: T; } @@ -32,31 +32,34 @@ declare module "../interfaces/type" { real: RealType } : never } + + interface Operations { + complex: {params: [T] | [T,T], returns: Complex} + } } -export type ComplexOp = {op?: 'complex', (a: T, b?: T): Complex} - export const complex = - (dep: Depends>): ComplexOp> => + (dep: Dependencies<'zero', T>): OpType<'complex', T | ZeroType> => (a, b) => ({re: a, im: b || dep.zero(a)}) export const zero = - (dep: Depends> & Depends>>): - ZeroOp> => + (dep: Dependencies<'zero', T> + & Dependencies<'complex', OpReturns<'zero', T>>): + OpType<'zero', Complex> => z => dep.complex(dep.zero(z.re), dep.zero(z.im)) export const one = - (dep: Depends> - & Depends> - & Depends|OneType>>): - OneOp> => + (dep: Dependencies<'one' | 'zero', T> + & Dependencies<'complex', OpReturns<'one' | 'zero', T>>): + OpType<'one', Complex> => z => dep.complex(dep.one(z.re), dep.zero(z.im)) export const nan = - (dep: Depends> & Depends>>): - NanOp> => + (dep: Dependencies<'nan', T> + & Dependencies<'complex', OpReturns<'nan', T>>): + OpType<'nan', Complex> => z => dep.complex(dep.nan(z.re), dep.nan(z.im)) export const re = - (dep: Depends>): ReOp> => + (dep: Dependencies<'re', T>): OpType<'re', Complex> => z => dep.re(z.re) diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index a7cf2b7..e0f55e8 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,6 +1,5 @@ -import type {Depends} from '../interfaces/type.js' -import type {MultiplyOp, SquareOp} from '../interfaces/arithmetic.js' +import type {Dependencies, OpType} from '../interfaces/type.js' export const square = - (dep: Depends>): SquareOp => + (dep: Dependencies<'multiply', T>): OpType<'square', T> => z => dep.multiply(z, z) diff --git a/src/generic/relational.ts b/src/generic/relational.ts index f47c392..2932e13 100644 --- a/src/generic/relational.ts +++ b/src/generic/relational.ts @@ -1,5 +1,5 @@ -import {Depends} from '../interfaces/type.js' -import type {EqualOp, UnequalOp} from '../interfaces/relational.js' +import {Dependencies, OpType} from '../interfaces/type.js' -export const unequal = (dep: Depends>): UnequalOp => +export const unequal = + (dep: Dependencies<'equal', T>): OpType<'unequal', T> => (x, y) => !dep.equal(x, y) diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts index 80e1155..1e4904e 100644 --- a/src/interfaces/arithmetic.ts +++ b/src/interfaces/arithmetic.ts @@ -1,25 +1,25 @@ import type {Complex} from '../Complex/type.js' import type {RealType, WithConstants, NaNType} from './type.js' -// Note: right now I've added an 'Op' suddix, -// so it is clear that the type holds the function type of an operation -// This is not necessary though, it is just a naming convention. -export type AddOp = {op?: 'add', (a: T, b: T): T} -export type AddRealOp = {op?: 'addReal', (a: T, b: RealType): T} -export type UnaryMinusOp = {op?: 'unaryMinus', (a: T): T} -export type ConjOp = {op?: 'conj', (a: T): T} -export type SubtractOp = {op?: 'subtract', (a: T, b: T): T} -export type MultiplyOp = {op?: 'multiply', (a: T, b: T): T} -export type AbsquareOp = {op?: 'absquare', (a: T): RealType} -export type ReciprocalOp = {op?: 'reciprocal', (a: T): T} -export type DivideOp = {op?: 'divide', (a: T, b: T): T} -export type DivideByRealOp = {op?: 'divideByReal', (a: T, b: RealType): T} -export type ConservativeSqrtOp = {op?: 'conservativeSqrt', (a: T): T} -export type SqrtOp = { - op?: 'sqrt', - (a: T): T extends Complex - ? Complex | NaNType>> - : T | Complex +type UnaryOperator = {params: [T], returns: T} +type BinaryOperator = {params: [T, T], returns: T} +declare module "./type" { + interface Operations { + add: BinaryOperator + unaryMinus: UnaryOperator + conj: UnaryOperator + subtract: BinaryOperator + multiply: BinaryOperator + square: UnaryOperator + absquare: {params: [T], returns: RealType} + reciprocal: UnaryOperator + divide: BinaryOperator + conservativeSqrt: UnaryOperator + sqrt: { + params: [T], + returns: T extends Complex + ? Complex> + : T | Complex + } + } } -export type SquareOp = {op?: 'square', (z: T): T} - diff --git a/src/interfaces/predicate.ts b/src/interfaces/predicate.ts index 37a69ea..16efd16 100644 --- a/src/interfaces/predicate.ts +++ b/src/interfaces/predicate.ts @@ -1,2 +1,9 @@ -export type IsRealOp = {op?: 'isReal', (a: T): boolean} -export type IsSquareOp = {op?: 'isSquare', (a: T): boolean} +// Warning: a module must have something besides just a "declare module" +// section; otherwise it is ignored. +export type UnaryPredicate = {params: [T], returns: boolean} +declare module "./type" { + interface Operations { + isReal: UnaryPredicate + isSquare: UnaryPredicate + } +} diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts index 29529ff..3149beb 100644 --- a/src/interfaces/relational.ts +++ b/src/interfaces/relational.ts @@ -1,2 +1,9 @@ -export type EqualOp = {op?: 'equal', (a: T, b: T): boolean} -export type UnequalOp = {op?: 'unequal', (a: T, b: T): boolean} +// Warning: a module must have something besides just a "declare module" +// section; otherwise it is ignored. +export type BinaryPredicate = {params: [T, T], returns: boolean} +declare module "./type" { + interface Operations { + equal: BinaryPredicate + unequal: BinaryPredicate + } +} diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index 1d7e103..8460235 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -1,3 +1,15 @@ +// Every typocomath type has some associated types; they need +// to be published as in the following interface. The key is the +// name of the type, and within the subinterface for that key, +// the type of the 'type' property is the actual TypeScript type +// we are associating the other properties to. Note the interface +// is generic with one parameter, corresponding to the fact that +// typocomath currently only allows types with a single generic parameter. +// This way, AssociatedTypes can give the associated types +// for a generic type instantiated with SubType. That's not necessary for +// the 'undefined' type (or if you look in the `numbers` subdirectory, +// the 'number' type either) or any concrete type, but that's OK, the +// generic parameter doesn't hurt in those cases. export interface AssociatedTypes { undefined: { type: undefined @@ -9,24 +21,46 @@ export interface AssociatedTypes { } type AssociatedTypeNames = keyof AssociatedTypes['undefined'] -export type Lookup = { +type ALookup = { [K in keyof AssociatedTypes]: T extends AssociatedTypes[K]['type'] ? AssociatedTypes[K][Name] : never }[keyof AssociatedTypes] -export type ZeroType = Lookup -export type OneType = Lookup +export type ZeroType = ALookup +export type OneType = ALookup export type WithConstants = T | ZeroType | OneType -export type NaNType = Lookup -export type RealType = Lookup +export type NaNType = ALookup +export type RealType = ALookup -export type ZeroOp = {op?: 'zero', (a: WithConstants): ZeroType} -export type OneOp = {op?: 'one', (a: WithConstants): OneType} -export type NanOp = {op?: 'nan', (a: T|NaNType): NaNType} -export type ReOp = {op?: 're', (a: T): RealType} +// The global signature patterns for all operations need to be published in the +// following interface. Each key is the name of an operation (but note that +// the Dispatcher will automatically merge operations that have the same +// name when the first underscore `_` and everything thereafter is stripped). +// The type of each key should be an interface with two properties: 'params' +// whose type is the type of the parameter list for the operation, and +// 'returns' whose type is the return type of the operation on those +// parameters. These types are generic in a parameter type T which should +// be interpreted as the type that the operation is supposed to "primarily" +// operate on, although note that some of the parameters and/or return types +// may depend on T rather than be exactly T. +// So note that the example 're' below provides essentially the same +// information that e.g. +// `type ReOp = (t: T) => RealType` +// would, but in a way that is much easier to manipulate in TypeScript, +// and it records the name of the operation as 're' also by virtue of the +// key 're' in the interface. +export interface Operations { + zero: {params: [WithConstants], returns: ZeroType} + one: {params: [WithConstants], returns: OneType} + nan: {params: [T | NaNType], returns: NaNType} + re: {params: [T], returns: RealType} +} -type NamedFunction = {op?: string, (...params: any[]): any} -export type Depends = - {[K in FuncType['op']]: FuncType} +type OpKey = keyof Operations + +export type OpReturns = Operations[Name]['returns'] +export type OpType = + (...args: Operations[Name]['params']) => OpReturns +export type Dependencies = {[K in Name]: OpType} diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 6a05fe8..a902f0c 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,27 +1,21 @@ import type {configDependency} from '../core/Config.js' -import type {ComplexOp} from '../Complex/type.js' -import type { - AddOp, ConjOp, SubtractOp, UnaryMinusOp, MultiplyOp, - AbsquareOp, ReciprocalOp, DivideOp, ConservativeSqrtOp, SqrtOp -} from '../interfaces/arithmetic.js' -import type {Depends} from '../interfaces/type.js' +import type {Dependencies, OpType} from '../interfaces/type.js' -export const add: AddOp = (a, b) => a + b -export const addReal = add -export const unaryMinus: UnaryMinusOp = a => -a -export const conj: ConjOp = a => a -export const subtract: SubtractOp = (a, b) => a - b -export const multiply: MultiplyOp = (a, b) => a * b -export const absquare: AbsquareOp = a => a * a -export const reciprocal: ReciprocalOp = a => 1 / a -export const divide: DivideOp = (a, b) => a / b -export const divideByReal = divide +export const add: OpType<'add', number> = (a, b) => a + b +export const unaryMinus: OpType<'unaryMinus', number> = a => -a +export const conj: OpType<'conj', number> = a => a +export const subtract: OpType<'subtract', number> = (a, b) => a - b +export const multiply: OpType<'multiply', number> = (a, b) => a * b +export const absquare: OpType<'absquare', number> = a => a * a +export const reciprocal: OpType<'reciprocal', number> = a => 1 / a +export const divide: OpType<'divide', number> = (a, b) => a / b const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a) -export const conservativeSqrt: ConservativeSqrtOp = basicSqrt +export const conservativeSqrt: OpType<'conservativeSqrt', number> = basicSqrt export const sqrt = - (dep: configDependency & Depends>): SqrtOp => { + (dep: configDependency & Dependencies<'complex', number>): + OpType<'sqrt', number> => { if (dep.config.predictable || !dep.complex) return basicSqrt return a => { if (isNaN(a)) return NaN diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index 2018e56..ae065f8 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,4 +1,4 @@ -import type { IsRealOp, IsSquareOp } from '../interfaces/predicate.js' +import type {OpType} from '../interfaces/type.js' -export const isReal: IsRealOp = (a) => true -export const isSquare: IsSquareOp = (a) => a >= 0 +export const isReal: OpType<'isReal', number> = (a) => true +export const isSquare: OpType<'isSquare', number> = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index 5dbb3c5..6bb0597 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,12 +1,11 @@ -import {Config} from '../core/Config.js' -import type {EqualOp} from '../interfaces/relational.js' +import {configDependency} from '../core/Config.js' +import {OpType} from '../interfaces/type.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 export const equal = - (dep: { - config: Config - }): EqualOp => (x, y) => { + (dep: configDependency): OpType<'equal', number> => + (x, y) => { const eps = dep.config.epsilon if (eps === null || eps === undefined) return x === y if (x === y) return true diff --git a/src/numbers/type.ts b/src/numbers/type.ts index ff1ff61..a8e6dfa 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,4 @@ -import type { OneOp, ZeroOp, NanOp, ReOp } from '../interfaces/type.js' +import type { OpType } from '../interfaces/type.js' export const number_type = { before: ['Complex'], @@ -18,9 +18,8 @@ declare module "../interfaces/type" { } } -// I don't like the redundancy of repeating 'zero' and 'ZeroOp', any -// way to eliminate that? -export const zero: ZeroOp = (a) => 0 -export const one: OneOp = (a) => 1 -export const nan: NanOp = (a) => NaN -export const re: ReOp = (a) => a +// I don't like the redundancy of repeating 'zero'; any way to eliminate that? +export const zero: OpType<'zero', number> = (a) => 0 +export const one: OpType<'one', number> = (a) => 1 +export const nan: OpType<'nan', number> = (a) => NaN +export const re: OpType<'re', number> = (a) => a -- 2.34.1 From 6d63d23498c77980c0945c35be3e70a914908a4a Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 24 Dec 2022 11:16:58 -0500 Subject: [PATCH 11/20] refactor: Streamline types and signature specfications The main mechanism for simplification was simply to assume that ZeroType and OneType will always be in T. That removed a lot of specialized typing, and presumably will be true in practice. Otherwise, removes extraneous type definitions and adds/clarifies a number of comments to hopefully make the scheme as clear as possible. --- src/Complex/arithmetic.ts | 21 +++++---- src/Complex/type.ts | 8 ++-- src/core/Dispatcher.ts | 82 +++-------------------------------- src/interfaces/arithmetic.ts | 6 +-- src/interfaces/type.ts | 84 ++++++++++++++++++++---------------- 5 files changed, 69 insertions(+), 132 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index f6089f4..a1be1f1 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -5,8 +5,10 @@ import type { declare module "../interfaces/type" { interface Operations { - // TODO: Make Dispatcher collapse operations that start with the same - // prefix up to a possible `_` + // TODO: Make Dispatcher collapse operations that match + // after removing any `_...` suffixes; the following should be + // additional dispatches for add and divide, not separate + // operations, in the final mathjs bundle. add_real: {params: [T, RealType], returns: T} divide_real: {params: [T, RealType], returns: T} } @@ -70,12 +72,15 @@ export const divide = OpType<'divide', Complex> => (w, z) => dep.multiply(w, dep.reciprocal(z)) +// The dependencies are slightly tricky here, because there are three types +// involved: Complex, T, and RealType, all of which might be different, +// and we have to get it straight which operations we need on each type, and +// in fact, we need `add_real` on both T and Complex, hence the dependency +// with a custom name, not generated via Dependencies<...> export const sqrt = - (dep: - Dependencies< - 'conservativeSqrt' | 'add' | 'unaryMinus' | 'equal', RealType> - & Dependencies<'zero' | 'add_real', T> - & Dependencies<'complex', T | ZeroType> + (dep: Dependencies<'add' | 'equal' | 'conservativeSqrt' | 'unaryMinus', + RealType> + & Dependencies<'zero' | 'add_real' | 'complex', T> & Dependencies<'absquare' | 're' | 'divide_real', Complex> & {add_complex_real: OpType<'add_real', Complex>}): OpType<'sqrt', Complex> => @@ -84,7 +89,7 @@ export const sqrt = const r = dep.re(z) const negr = dep.unaryMinus(r) if (dep.equal(myabs, negr)) { - // pure imaginary square root; z.im already sero + // pure imaginary square root; z.im already zero return dep.complex( dep.zero(z.re), dep.add_real(z.im, dep.conservativeSqrt(negr))) } diff --git a/src/Complex/type.ts b/src/Complex/type.ts index 8c669ea..379a52a 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,6 +1,4 @@ -import { - joinTypes, typeOfDependency, Dependency, -} from '../core/Dispatcher.js' +import {joinTypes, typeOfDependency} from '../core/Dispatcher.js' import type { ZeroType, OneType, NaNType, Dependencies, OpType, OpReturns } from '../interfaces/type.js' @@ -15,7 +13,7 @@ export const Complex_type = { infer: (dep: typeOfDependency) => (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), from: { - T: (dep: Dependency<'zero', [T]>) => (t: T) => + T: (dep: Dependencies<'zero', T>) => (t: T) => ({ re: t, im: dep.zero(t) }), Complex: (dep: { convert: (from: U) => T }) => (z: Complex) => ({ re: dep.convert(z.re), im: dep.convert(z.im) }) @@ -39,7 +37,7 @@ declare module "../interfaces/type" { } export const complex = - (dep: Dependencies<'zero', T>): OpType<'complex', T | ZeroType> => + (dep: Dependencies<'zero', T>): OpType<'complex', T> => (a, b) => ({re: a, im: b || dep.zero(a)}) export const zero = diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 9b005d9..def1bd3 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -9,91 +9,19 @@ type TypeName = string type Parameter = TypeName -type InputSignature = Parameter[] +type Signature = Parameter[] type DependenciesType = Record +// A "canned" dependency for a builtin function: export type typeOfDependency = {typeOf: (x: unknown) => TypeName} -// 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 basic signature with concrete types -export type Signature = - CandidateParams extends ActualParams ? Returns : never - +// Utility needed in type definitions //dummy implementation for now export function joinTypes(a: TypeName, b: TypeName) { if (a === b) return a return 'any' } -// Used to filter keys that match a given operation name -type BeginsWith = Name | `${Name}_${string}` - -// 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] - -// 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]: ImpType} - // Now types used in the Dispatcher class itself type TypeSpecification = { @@ -110,9 +38,9 @@ type SpecificationsGroup = Record export class Dispatcher { installSpecification( name: string, - signature: InputSignature, + signature: Signature, 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/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts index 1e4904e..c497b5d 100644 --- a/src/interfaces/arithmetic.ts +++ b/src/interfaces/arithmetic.ts @@ -1,5 +1,5 @@ import type {Complex} from '../Complex/type.js' -import type {RealType, WithConstants, NaNType} from './type.js' +import type {RealType} from './type.js' type UnaryOperator = {params: [T], returns: T} type BinaryOperator = {params: [T, T], returns: T} @@ -17,9 +17,7 @@ declare module "./type" { conservativeSqrt: UnaryOperator sqrt: { params: [T], - returns: T extends Complex - ? Complex> - : T | Complex + returns: T extends Complex ? T : T | Complex } } } diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index 8460235..b34c68d 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -1,15 +1,18 @@ -// Every typocomath type has some associated types; they need -// to be published as in the following interface. The key is the -// name of the type, and within the subinterface for that key, -// the type of the 'type' property is the actual TypeScript type -// we are associating the other properties to. Note the interface -// is generic with one parameter, corresponding to the fact that -// typocomath currently only allows types with a single generic parameter. -// This way, AssociatedTypes can give the associated types -// for a generic type instantiated with SubType. That's not necessary for -// the 'undefined' type (or if you look in the `numbers` subdirectory, -// the 'number' type either) or any concrete type, but that's OK, the -// generic parameter doesn't hurt in those cases. +/***** + * Every typocomath type has some associated types; they need + * to be published in the following interface. The key is the + * name of the type, and within the subinterface for that key, + * the type of the 'type' property is the actual TypeScript type + * we are associating the other properties to. Note the interface + * is generic with one parameter, corresponding to the fact that + * typocomath currently only allows generic types with a single + * generic parameter. This way, AssociatedTypes can give the + * associated types for a generic type instantiated with SubType. + * That's not necessary for the 'undefined' type (or if you look in the + * `numbers` subdirectory, the 'number' type) or any concrete type, + * but that's OK, the generic parameter doesn't hurt in those cases. + ****/ + export interface AssociatedTypes { undefined: { type: undefined @@ -26,34 +29,41 @@ type ALookup = { T extends AssociatedTypes[K]['type'] ? AssociatedTypes[K][Name] : never }[keyof AssociatedTypes] -export type ZeroType = ALookup -export type OneType = ALookup -export type WithConstants = T | ZeroType | OneType +// For everything to compile, zero and one must be subtypes of T: +export type ZeroType = ALookup & T +export type OneType = ALookup & T +// But I believe 'nan' really might not be, like I think we will have to use +// 'undefined' for the nan of 'bigint', as it has nothing at all like NaN, +// so don't force it: export type NaNType = ALookup export type RealType = ALookup -// The global signature patterns for all operations need to be published in the -// following interface. Each key is the name of an operation (but note that -// the Dispatcher will automatically merge operations that have the same -// name when the first underscore `_` and everything thereafter is stripped). -// The type of each key should be an interface with two properties: 'params' -// whose type is the type of the parameter list for the operation, and -// 'returns' whose type is the return type of the operation on those -// parameters. These types are generic in a parameter type T which should -// be interpreted as the type that the operation is supposed to "primarily" -// operate on, although note that some of the parameters and/or return types -// may depend on T rather than be exactly T. -// So note that the example 're' below provides essentially the same -// information that e.g. -// `type ReOp = (t: T) => RealType` -// would, but in a way that is much easier to manipulate in TypeScript, -// and it records the name of the operation as 're' also by virtue of the -// key 're' in the interface. +/***** + * The global signature patterns for all operations need to be published in the + * following interface. Each key is the name of an operation (but note that + * the Dispatcher will automatically merge operations that have the same + * name when the first underscore `_` and everything thereafter is stripped). + * The type of each key should be an interface with two properties: 'params' + * whose type is the type of the parameter list for the operation, and + * 'returns' whose type is the return type of the operation on those + * parameters. These types are generic in a parameter type T which should + * be interpreted as the type that the operation is supposed to "primarily" + * operate on, although note that some of the parameters and/or return types + * may depend on T rather than be exactly T. + * So note that the example 're' below provides essentially the same + * information that e.g. + * `type ReOp = (t: T) => RealType` + * would, but in a way that is much easier to manipulate in TypeScript, + * and it records the name of the operation as 're' also by virtue of the + * key 're' in the interface. + ****/ export interface Operations { - zero: {params: [WithConstants], returns: ZeroType} - one: {params: [WithConstants], returns: OneType} - nan: {params: [T | NaNType], returns: NaNType} - re: {params: [T], returns: RealType} + zero: {params: [T], returns: ZeroType} + one: {params: [T], returns: OneType} + // nan needs to be able to operate on its own output for everything + // else to compile. That's why its parameter type is widened: + nan: {params: [T | NaNType], returns: NaNType} + re: {params: [T], returns: RealType} } type OpKey = keyof Operations @@ -62,5 +72,3 @@ export type OpReturns = Operations[Name]['returns'] export type OpType = (...args: Operations[Name]['params']) => OpReturns export type Dependencies = {[K in Name]: OpType} - - -- 2.34.1 From 63c2d448c18b7be1af2146deeff968a375c92745 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 12:03:31 +0100 Subject: [PATCH 12/20] docs: describe how to run the prototype --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 34649c2..d15fcc4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # typocomath -A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. \ No newline at end of file +A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. + +To build and run the prototype, run: + +``` +npx tsc +node obj +``` -- 2.34.1 From a0b21181e6906cc17ae38965873587513ed35841 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 12:12:28 +0100 Subject: [PATCH 13/20] chore: rename `OpType` to `Signature` --- src/Complex/arithmetic.ts | 30 +++++++++++++++--------------- src/Complex/predicate.ts | 6 +++--- src/Complex/relational.ts | 4 ++-- src/Complex/type.ts | 20 ++++++++++---------- src/generic/arithmetic.ts | 4 ++-- src/generic/relational.ts | 4 ++-- src/interfaces/arithmetic.ts | 2 +- src/interfaces/predicate.ts | 2 +- src/interfaces/relational.ts | 2 +- src/interfaces/type.ts | 12 ++++++------ src/numbers/arithmetic.ts | 22 +++++++++++----------- src/numbers/predicate.ts | 6 +++--- src/numbers/relational.ts | 4 ++-- src/numbers/type.ts | 10 +++++----- 14 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index a1be1f1..e11d91e 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,10 +1,10 @@ import {Complex} from './type.js' import type { - Dependencies, OpType, OpReturns, RealType, ZeroType + Dependencies, Signature, Returns, RealType, ZeroType } from '../interfaces/type.js' declare module "../interfaces/type" { - interface Operations { + interface Signatures { // TODO: Make Dispatcher collapse operations that match // after removing any `_...` suffixes; the following should be // additional dispatches for add and divide, not separate @@ -15,33 +15,33 @@ declare module "../interfaces/type" { } export const add = - (dep: Dependencies<'add' | 'complex', T>): OpType<'add', Complex> => + (dep: Dependencies<'add' | 'complex', T>): Signature<'add', Complex> => (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) export const add_real = (dep: Dependencies<'add_real' | 'complex', T>): - OpType<'add_real', Complex> => + Signature<'add_real', Complex> => (z, r) => dep.complex(dep.add_real(z.re, r), z.im) export const unaryMinus = (dep: Dependencies<'unaryMinus' | 'complex', T>): - OpType<'unaryMinus', Complex> => + Signature<'unaryMinus', Complex> => z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) export const conj = (dep: Dependencies<'unaryMinus' | 'conj' | 'complex', T>): - OpType<'conj', Complex> => + Signature<'conj', Complex> => z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im)) export const subtract = (dep: Dependencies<'subtract' | 'complex', T>): - OpType<'subtract', Complex> => + Signature<'subtract', Complex> => (w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) export const multiply = (dep: Dependencies< 'add' | 'subtract' | 'multiply' | 'conj' | 'complex', T>): - OpType<'multiply', Complex> => + Signature<'multiply', Complex> => (w, z) => { const mult = dep.multiply const realpart = dep.subtract( @@ -53,23 +53,23 @@ export const multiply = export const absquare = (dep: Dependencies<'absquare', T> - & Dependencies<'add', OpReturns<'absquare', T>>): - OpType<'absquare', Complex> => + & Dependencies<'add', Returns<'absquare', T>>): + Signature<'absquare', Complex> => z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = (dep: Dependencies<'divide_real' | 'complex', T>): - OpType<'divide_real', Complex> => + Signature<'divide_real', Complex> => (z, r) => dep.complex(dep.divide_real(z.re, r), dep.divide_real(z.im, r)) export const reciprocal = (dep: Dependencies<'conj' | 'absquare' | 'divide_real', Complex>): - OpType<'reciprocal', Complex> => + Signature<'reciprocal', Complex> => z => dep.divide_real(dep.conj(z), dep.absquare(z)) export const divide = (dep: Dependencies<'multiply' | 'reciprocal', Complex>): - OpType<'divide', Complex> => + Signature<'divide', Complex> => (w, z) => dep.multiply(w, dep.reciprocal(z)) // The dependencies are slightly tricky here, because there are three types @@ -82,8 +82,8 @@ export const sqrt = RealType> & Dependencies<'zero' | 'add_real' | 'complex', T> & Dependencies<'absquare' | 're' | 'divide_real', Complex> - & {add_complex_real: OpType<'add_real', Complex>}): - OpType<'sqrt', Complex> => + & {add_complex_real: Signature<'add_real', Complex>}): + Signature<'sqrt', Complex> => z => { const myabs = dep.conservativeSqrt(dep.absquare(z)) const r = dep.re(z) diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index e6c4c7f..ffcefaa 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,9 +1,9 @@ import {Complex} from './type.js' -import type {Dependencies, OpType} from '../interfaces/type.js' +import type {Dependencies, Signature} from '../interfaces/type.js' export const isReal = (dep: Dependencies<'add' | 'equal' | 'isReal', T>): - OpType<'isReal', Complex> => + Signature<'isReal', Complex> => z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) -export const isSquare: OpType<'isSquare', Complex> = z => true // FIXME: not correct for Complex once we get there +export const isSquare: Signature<'isSquare', Complex> = z => true // FIXME: not correct for Complex once we get there diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts index d0b9e52..78550d7 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,6 +1,6 @@ import {Complex} from './type.js' -import {Dependencies, OpType} from '../interfaces/type.js' +import {Dependencies, Signature} from '../interfaces/type.js' export const equal = - (dep: Dependencies<'equal', T>): OpType<'equal', Complex> => + (dep: Dependencies<'equal', T>): Signature<'equal', Complex> => (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index 379a52a..43c79fe 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,6 +1,6 @@ import {joinTypes, typeOfDependency} from '../core/Dispatcher.js' import type { - ZeroType, OneType, NaNType, Dependencies, OpType, OpReturns + ZeroType, OneType, NaNType, Dependencies, Signature, Returns } from '../interfaces/type.js' export type Complex = { re: T; im: T; } @@ -31,33 +31,33 @@ declare module "../interfaces/type" { } : never } - interface Operations { + interface Signatures { complex: {params: [T] | [T,T], returns: Complex} } } export const complex = - (dep: Dependencies<'zero', T>): OpType<'complex', T> => + (dep: Dependencies<'zero', T>): Signature<'complex', T> => (a, b) => ({re: a, im: b || dep.zero(a)}) export const zero = (dep: Dependencies<'zero', T> - & Dependencies<'complex', OpReturns<'zero', T>>): - OpType<'zero', Complex> => + & Dependencies<'complex', Returns<'zero', T>>): + Signature<'zero', Complex> => z => dep.complex(dep.zero(z.re), dep.zero(z.im)) export const one = (dep: Dependencies<'one' | 'zero', T> - & Dependencies<'complex', OpReturns<'one' | 'zero', T>>): - OpType<'one', Complex> => + & Dependencies<'complex', Returns<'one' | 'zero', T>>): + Signature<'one', Complex> => z => dep.complex(dep.one(z.re), dep.zero(z.im)) export const nan = (dep: Dependencies<'nan', T> - & Dependencies<'complex', OpReturns<'nan', T>>): - OpType<'nan', Complex> => + & Dependencies<'complex', Returns<'nan', T>>): + Signature<'nan', Complex> => z => dep.complex(dep.nan(z.re), dep.nan(z.im)) export const re = - (dep: Dependencies<'re', T>): OpType<'re', Complex> => + (dep: Dependencies<'re', T>): Signature<'re', Complex> => z => dep.re(z.re) diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index e0f55e8..78ad57d 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,5 +1,5 @@ -import type {Dependencies, OpType} from '../interfaces/type.js' +import type {Dependencies, Signature} from '../interfaces/type.js' export const square = - (dep: Dependencies<'multiply', T>): OpType<'square', T> => + (dep: Dependencies<'multiply', T>): Signature<'square', T> => z => dep.multiply(z, z) diff --git a/src/generic/relational.ts b/src/generic/relational.ts index 2932e13..714cb49 100644 --- a/src/generic/relational.ts +++ b/src/generic/relational.ts @@ -1,5 +1,5 @@ -import {Dependencies, OpType} from '../interfaces/type.js' +import {Dependencies, Signature} from '../interfaces/type.js' export const unequal = - (dep: Dependencies<'equal', T>): OpType<'unequal', T> => + (dep: Dependencies<'equal', T>): Signature<'unequal', T> => (x, y) => !dep.equal(x, y) diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts index c497b5d..f77ceb1 100644 --- a/src/interfaces/arithmetic.ts +++ b/src/interfaces/arithmetic.ts @@ -4,7 +4,7 @@ import type {RealType} from './type.js' type UnaryOperator = {params: [T], returns: T} type BinaryOperator = {params: [T, T], returns: T} declare module "./type" { - interface Operations { + interface Signatures { add: BinaryOperator unaryMinus: UnaryOperator conj: UnaryOperator diff --git a/src/interfaces/predicate.ts b/src/interfaces/predicate.ts index 16efd16..b4c7393 100644 --- a/src/interfaces/predicate.ts +++ b/src/interfaces/predicate.ts @@ -2,7 +2,7 @@ // section; otherwise it is ignored. export type UnaryPredicate = {params: [T], returns: boolean} declare module "./type" { - interface Operations { + interface Signatures { isReal: UnaryPredicate isSquare: UnaryPredicate } diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts index 3149beb..9b511b0 100644 --- a/src/interfaces/relational.ts +++ b/src/interfaces/relational.ts @@ -2,7 +2,7 @@ // section; otherwise it is ignored. export type BinaryPredicate = {params: [T, T], returns: boolean} declare module "./type" { - interface Operations { + interface Signatures { equal: BinaryPredicate unequal: BinaryPredicate } diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index b34c68d..4596d7f 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -57,7 +57,7 @@ export type RealType = ALookup * and it records the name of the operation as 're' also by virtue of the * key 're' in the interface. ****/ -export interface Operations { +export interface Signatures { zero: {params: [T], returns: ZeroType} one: {params: [T], returns: OneType} // nan needs to be able to operate on its own output for everything @@ -66,9 +66,9 @@ export interface Operations { re: {params: [T], returns: RealType} } -type OpKey = keyof Operations +type SignatureKey = keyof Signatures -export type OpReturns = Operations[Name]['returns'] -export type OpType = - (...args: Operations[Name]['params']) => OpReturns -export type Dependencies = {[K in Name]: OpType} +export type Returns = Signatures[Name]['returns'] +export type Signature = + (...args: Signatures[Name]['params']) => Returns +export type Dependencies = {[K in Name]: Signature} diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index a902f0c..11da5c2 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,21 +1,21 @@ import type {configDependency} from '../core/Config.js' -import type {Dependencies, OpType} from '../interfaces/type.js' +import type {Dependencies, Signature} from '../interfaces/type.js' -export const add: OpType<'add', number> = (a, b) => a + b -export const unaryMinus: OpType<'unaryMinus', number> = a => -a -export const conj: OpType<'conj', number> = a => a -export const subtract: OpType<'subtract', number> = (a, b) => a - b -export const multiply: OpType<'multiply', number> = (a, b) => a * b -export const absquare: OpType<'absquare', number> = a => a * a -export const reciprocal: OpType<'reciprocal', number> = a => 1 / a -export const divide: OpType<'divide', number> = (a, b) => a / b +export const add: Signature<'add', number> = (a, b) => a + b +export const unaryMinus: Signature<'unaryMinus', number> = a => -a +export const conj: Signature<'conj', number> = a => a +export const subtract: Signature<'subtract', number> = (a, b) => a - b +export const multiply: Signature<'multiply', number> = (a, b) => a * b +export const absquare: Signature<'absquare', number> = a => a * a +export const reciprocal: Signature<'reciprocal', number> = a => 1 / a +export const divide: Signature<'divide', number> = (a, b) => a / b const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a) -export const conservativeSqrt: OpType<'conservativeSqrt', number> = basicSqrt +export const conservativeSqrt: Signature<'conservativeSqrt', number> = basicSqrt export const sqrt = (dep: configDependency & Dependencies<'complex', number>): - OpType<'sqrt', number> => { + Signature<'sqrt', number> => { if (dep.config.predictable || !dep.complex) return basicSqrt return a => { if (isNaN(a)) return NaN diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index ae065f8..03bd80f 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,4 +1,4 @@ -import type {OpType} from '../interfaces/type.js' +import type {Signature} from '../interfaces/type.js' -export const isReal: OpType<'isReal', number> = (a) => true -export const isSquare: OpType<'isSquare', number> = (a) => a >= 0 +export const isReal: Signature<'isReal', number> = (a) => true +export const isSquare: Signature<'isSquare', number> = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index 6bb0597..8f1ac92 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,10 +1,10 @@ import {configDependency} from '../core/Config.js' -import {OpType} from '../interfaces/type.js' +import {Signature} from '../interfaces/type.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 export const equal = - (dep: configDependency): OpType<'equal', number> => + (dep: configDependency): Signature<'equal', number> => (x, y) => { const eps = dep.config.epsilon if (eps === null || eps === undefined) return x === y diff --git a/src/numbers/type.ts b/src/numbers/type.ts index a8e6dfa..11e5d1c 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,4 @@ -import type { OpType } from '../interfaces/type.js' +import type { Signature } from '../interfaces/type.js' export const number_type = { before: ['Complex'], @@ -19,7 +19,7 @@ declare module "../interfaces/type" { } // I don't like the redundancy of repeating 'zero'; any way to eliminate that? -export const zero: OpType<'zero', number> = (a) => 0 -export const one: OpType<'one', number> = (a) => 1 -export const nan: OpType<'nan', number> = (a) => NaN -export const re: OpType<'re', number> = (a) => a +export const zero: Signature<'zero', number> = (a) => 0 +export const one: Signature<'one', number> = (a) => 1 +export const nan: Signature<'nan', number> = (a) => NaN +export const re: Signature<'re', number> = (a) => a -- 2.34.1 From 1b9d6b64287cdc4629a774ebee0367cfe7a90cc1 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 12:28:24 +0100 Subject: [PATCH 14/20] chore: use direct function definitions instead of `{ params, returns }` --- src/Complex/arithmetic.ts | 4 ++-- src/Complex/type.ts | 2 +- src/interfaces/arithmetic.ts | 27 +++++++++++---------------- src/interfaces/predicate.ts | 7 ++++--- src/interfaces/relational.ts | 6 +++--- src/interfaces/type.ts | 13 ++++++------- 6 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index e11d91e..cc3d49b 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -9,8 +9,8 @@ declare module "../interfaces/type" { // after removing any `_...` suffixes; the following should be // additional dispatches for add and divide, not separate // operations, in the final mathjs bundle. - add_real: {params: [T, RealType], returns: T} - divide_real: {params: [T, RealType], returns: T} + add_real: (a: T, b: RealType) => T + divide_real: (a: T, b: RealType) => T } } diff --git a/src/Complex/type.ts b/src/Complex/type.ts index 43c79fe..50a1314 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -32,7 +32,7 @@ declare module "../interfaces/type" { } interface Signatures { - complex: {params: [T] | [T,T], returns: Complex} + complex: ((re: T) => Complex) | ((re: T, im: T) => Complex) } } diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts index f77ceb1..df2d765 100644 --- a/src/interfaces/arithmetic.ts +++ b/src/interfaces/arithmetic.ts @@ -1,23 +1,18 @@ import type {Complex} from '../Complex/type.js' import type {RealType} from './type.js' -type UnaryOperator = {params: [T], returns: T} -type BinaryOperator = {params: [T, T], returns: T} declare module "./type" { interface Signatures { - add: BinaryOperator - unaryMinus: UnaryOperator - conj: UnaryOperator - subtract: BinaryOperator - multiply: BinaryOperator - square: UnaryOperator - absquare: {params: [T], returns: RealType} - reciprocal: UnaryOperator - divide: BinaryOperator - conservativeSqrt: UnaryOperator - sqrt: { - params: [T], - returns: T extends Complex ? T : T | Complex - } + add: (a: T, b: T) => T + unaryMinus: (a: T) => T + conj: (a: T) => T + subtract: (a: T, b: T) => T + multiply: (a: T, b: T) => T + square: (a: T) => T + absquare: (a: T) => RealType + reciprocal: (a: T) => T + divide: (a: T, b: T) => T + conservativeSqrt: (a: T) => T + sqrt: (a: T)=> T extends Complex ? T : T | Complex } } diff --git a/src/interfaces/predicate.ts b/src/interfaces/predicate.ts index b4c7393..93d3b4a 100644 --- a/src/interfaces/predicate.ts +++ b/src/interfaces/predicate.ts @@ -1,9 +1,10 @@ // Warning: a module must have something besides just a "declare module" // section; otherwise it is ignored. -export type UnaryPredicate = {params: [T], returns: boolean} +export type UnaryPredicate = (a: T) => boolean + declare module "./type" { interface Signatures { - isReal: UnaryPredicate - isSquare: UnaryPredicate + isReal: (a: T) => boolean + isSquare: (a: T) => boolean } } diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts index 9b511b0..e2aa3a1 100644 --- a/src/interfaces/relational.ts +++ b/src/interfaces/relational.ts @@ -1,9 +1,9 @@ // Warning: a module must have something besides just a "declare module" // section; otherwise it is ignored. -export type BinaryPredicate = {params: [T, T], returns: boolean} +export type BinaryPredicate = (a: T, b: T) => T declare module "./type" { interface Signatures { - equal: BinaryPredicate - unequal: BinaryPredicate + equal: (a: T, b: T) => boolean + unequal: (a: T, b: T) => boolean } } diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index 4596d7f..c68c98a 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -58,17 +58,16 @@ export type RealType = ALookup * key 're' in the interface. ****/ export interface Signatures { - zero: {params: [T], returns: ZeroType} - one: {params: [T], returns: OneType} + zero: (a: T) => ZeroType + one: (a: T) => OneType // nan needs to be able to operate on its own output for everything // else to compile. That's why its parameter type is widened: - nan: {params: [T | NaNType], returns: NaNType} - re: {params: [T], returns: RealType} + nan: (a: T | NaNType) => NaNType + re: (a: T) => RealType } type SignatureKey = keyof Signatures -export type Returns = Signatures[Name]['returns'] -export type Signature = - (...args: Signatures[Name]['params']) => Returns +export type Signature = Signatures[Name] +export type Returns = ReturnType[Name]> export type Dependencies = {[K in Name]: Signature} -- 2.34.1 From d147d0a576186f6edfb9fc667f4f755f98470b6b Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 12:35:37 +0100 Subject: [PATCH 15/20] chore: implement `AliasOf` --- src/Complex/arithmetic.ts | 32 ++++++++++++++++---------------- src/interfaces/type.ts | 2 ++ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index cc3d49b..900e2a1 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,6 +1,6 @@ import {Complex} from './type.js' import type { - Dependencies, Signature, Returns, RealType, ZeroType + Dependencies, Signature, Returns, RealType, AliasOf } from '../interfaces/type.js' declare module "../interfaces/type" { @@ -9,8 +9,8 @@ declare module "../interfaces/type" { // after removing any `_...` suffixes; the following should be // additional dispatches for add and divide, not separate // operations, in the final mathjs bundle. - add_real: (a: T, b: RealType) => T - divide_real: (a: T, b: RealType) => T + addReal: AliasOf<'add', (a: T, b: RealType) => T> + divideReal: AliasOf<'divide', (a: T, b: RealType) => T> } } @@ -19,9 +19,9 @@ export const add = (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) export const add_real = - (dep: Dependencies<'add_real' | 'complex', T>): - Signature<'add_real', Complex> => - (z, r) => dep.complex(dep.add_real(z.re, r), z.im) + (dep: Dependencies<'addReal' | 'complex', T>): + Signature<'addReal', Complex> => + (z, r) => dep.complex(dep.addReal(z.re, r), z.im) export const unaryMinus = (dep: Dependencies<'unaryMinus' | 'complex', T>): @@ -58,14 +58,14 @@ export const absquare = z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) export const divideByReal = - (dep: Dependencies<'divide_real' | 'complex', T>): - Signature<'divide_real', Complex> => - (z, r) => dep.complex(dep.divide_real(z.re, r), dep.divide_real(z.im, r)) + (dep: Dependencies<'divideReal' | 'complex', T>): + Signature<'divideReal', Complex> => + (z, r) => dep.complex(dep.divideReal(z.re, r), dep.divideReal(z.im, r)) export const reciprocal = - (dep: Dependencies<'conj' | 'absquare' | 'divide_real', Complex>): + (dep: Dependencies<'conj' | 'absquare' | 'divideReal', Complex>): Signature<'reciprocal', Complex> => - z => dep.divide_real(dep.conj(z), dep.absquare(z)) + z => dep.divideReal(dep.conj(z), dep.absquare(z)) export const divide = (dep: Dependencies<'multiply' | 'reciprocal', Complex>): @@ -80,9 +80,9 @@ export const divide = export const sqrt = (dep: Dependencies<'add' | 'equal' | 'conservativeSqrt' | 'unaryMinus', RealType> - & Dependencies<'zero' | 'add_real' | 'complex', T> - & Dependencies<'absquare' | 're' | 'divide_real', Complex> - & {add_complex_real: Signature<'add_real', Complex>}): + & Dependencies<'zero' | 'addReal' | 'complex', T> + & Dependencies<'absquare' | 're' | 'divideReal', Complex> + & {add_complex_real: Signature<'addReal', Complex>}): Signature<'sqrt', Complex> => z => { const myabs = dep.conservativeSqrt(dep.absquare(z)) @@ -91,12 +91,12 @@ export const sqrt = if (dep.equal(myabs, negr)) { // pure imaginary square root; z.im already zero return dep.complex( - dep.zero(z.re), dep.add_real(z.im, dep.conservativeSqrt(negr))) + dep.zero(z.re), dep.addReal(z.im, dep.conservativeSqrt(negr))) } const num = dep.add_complex_real(z, myabs) const denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) const denom = dep.conservativeSqrt(denomsq) - return dep.divide_real(num, denom) + return dep.divideReal(num, denom) } export const conservativeSqrt = sqrt diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index c68c98a..4c91d9b 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -71,3 +71,5 @@ type SignatureKey = keyof Signatures export type Signature = Signatures[Name] export type Returns = ReturnType[Name]> export type Dependencies = {[K in Name]: Signature} + +export type AliasOf = T & {aliasOf?: Name} -- 2.34.1 From 0ea786dcc22985df8559aefc31cc3aa464783df2 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 14:06:30 +0100 Subject: [PATCH 16/20] chore: show how to inject multiple instances of the same function (WIP) --- src/Complex/arithmetic.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 900e2a1..93d1559 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -78,11 +78,14 @@ export const divide = // in fact, we need `add_real` on both T and Complex, hence the dependency // with a custom name, not generated via Dependencies<...> export const sqrt = - (dep: Dependencies<'add' | 'equal' | 'conservativeSqrt' | 'unaryMinus', - RealType> - & Dependencies<'zero' | 'addReal' | 'complex', T> + (dep: Dependencies<'equal' | 'conservativeSqrt' | 'unaryMinus', RealType> + & Dependencies<'zero' | 'complex', T> & Dependencies<'absquare' | 're' | 'divideReal', Complex> - & {add_complex_real: Signature<'addReal', Complex>}): + & { + addNumber: Signature<'addReal', T>, // TODO: should be possible to use Signature<'add'> here + addReal: Signature<'add', RealType>, + addComplex: Signature<'addReal', Complex> // TODO: should be possible to use Signature<'add'> here + }): Signature<'sqrt', Complex> => z => { const myabs = dep.conservativeSqrt(dep.absquare(z)) @@ -91,10 +94,10 @@ export const sqrt = if (dep.equal(myabs, negr)) { // pure imaginary square root; z.im already zero return dep.complex( - dep.zero(z.re), dep.addReal(z.im, dep.conservativeSqrt(negr))) + dep.zero(z.re), dep.addNumber(z.im, dep.conservativeSqrt(negr))) } - const num = dep.add_complex_real(z, myabs) - const denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) + const num = dep.addComplex(z, myabs) + const denomsq = dep.addReal(dep.addReal(myabs, myabs), dep.addReal(r, r)) const denom = dep.conservativeSqrt(denomsq) return dep.divideReal(num, denom) } -- 2.34.1 From e468ec72dd0c3510218bab4e2ec89c508cd6707d Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 14:32:17 +0100 Subject: [PATCH 17/20] chore: rephrase two todo's --- src/Complex/arithmetic.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 93d1559..0d403f4 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -82,9 +82,9 @@ export const sqrt = & Dependencies<'zero' | 'complex', T> & Dependencies<'absquare' | 're' | 'divideReal', Complex> & { - addNumber: Signature<'addReal', T>, // TODO: should be possible to use Signature<'add'> here + addNumber: Signature<'addReal', T>, // TODO: should use Signature<'add'> here addReal: Signature<'add', RealType>, - addComplex: Signature<'addReal', Complex> // TODO: should be possible to use Signature<'add'> here + addComplex: Signature<'addReal', Complex> // TODO: should use Signature<'add'> here }): Signature<'sqrt', Complex> => z => { -- 2.34.1 From bd05dc926776db4e49f107c8d27922e488a207e2 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 4 Jan 2023 14:32:44 +0100 Subject: [PATCH 18/20] chore: make SignatureKey generic instead of using unknown --- src/interfaces/type.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interfaces/type.ts b/src/interfaces/type.ts index 4c91d9b..9e6870d 100644 --- a/src/interfaces/type.ts +++ b/src/interfaces/type.ts @@ -66,10 +66,10 @@ export interface Signatures { re: (a: T) => RealType } -type SignatureKey = keyof Signatures +type SignatureKey = keyof Signatures -export type Signature = Signatures[Name] -export type Returns = ReturnType[Name]> -export type Dependencies = {[K in Name]: Signature} +export type Signature, T> = Signatures[Name] +export type Returns, T> = ReturnType[Name]> +export type Dependencies, T> = {[K in Name]: Signature} export type AliasOf = T & {aliasOf?: Name} -- 2.34.1 From 637e339b170268ef8103b635d65a2b92b4728b10 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 21 Jan 2023 20:21:05 -0500 Subject: [PATCH 19/20] refactor: clarify implementation names in sqrt Also removes some obsolete comments --- src/Complex/arithmetic.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 0d403f4..90c8eab 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -5,10 +5,6 @@ import type { declare module "../interfaces/type" { interface Signatures { - // TODO: Make Dispatcher collapse operations that match - // after removing any `_...` suffixes; the following should be - // additional dispatches for add and divide, not separate - // operations, in the final mathjs bundle. addReal: AliasOf<'add', (a: T, b: RealType) => T> divideReal: AliasOf<'divide', (a: T, b: RealType) => T> } @@ -18,7 +14,7 @@ export const add = (dep: Dependencies<'add' | 'complex', T>): Signature<'add', Complex> => (w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im)) -export const add_real = +export const addReal = (dep: Dependencies<'addReal' | 'complex', T>): Signature<'addReal', Complex> => (z, r) => dep.complex(dep.addReal(z.re, r), z.im) @@ -57,7 +53,7 @@ export const absquare = Signature<'absquare', Complex> => z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) -export const divideByReal = +export const divideReal = (dep: Dependencies<'divideReal' | 'complex', T>): Signature<'divideReal', Complex> => (z, r) => dep.complex(dep.divideReal(z.re, r), dep.divideReal(z.im, r)) @@ -75,16 +71,16 @@ export const divide = // The dependencies are slightly tricky here, because there are three types // involved: Complex, T, and RealType, all of which might be different, // and we have to get it straight which operations we need on each type, and -// in fact, we need `add_real` on both T and Complex, hence the dependency +// in fact, we need `addReal` on both T and Complex, hence the dependency // with a custom name, not generated via Dependencies<...> export const sqrt = (dep: Dependencies<'equal' | 'conservativeSqrt' | 'unaryMinus', RealType> & Dependencies<'zero' | 'complex', T> & Dependencies<'absquare' | 're' | 'divideReal', Complex> & { - addNumber: Signature<'addReal', T>, // TODO: should use Signature<'add'> here - addReal: Signature<'add', RealType>, - addComplex: Signature<'addReal', Complex> // TODO: should use Signature<'add'> here + addTR: Signature<'addReal', T>, + addRR: Signature<'add', RealType>, + addCR: Signature<'addReal', Complex> }): Signature<'sqrt', Complex> => z => { @@ -94,10 +90,10 @@ export const sqrt = if (dep.equal(myabs, negr)) { // pure imaginary square root; z.im already zero return dep.complex( - dep.zero(z.re), dep.addNumber(z.im, dep.conservativeSqrt(negr))) + dep.zero(z.re), dep.addTR(z.im, dep.conservativeSqrt(negr))) } - const num = dep.addComplex(z, myabs) - const denomsq = dep.addReal(dep.addReal(myabs, myabs), dep.addReal(r, r)) + const num = dep.addCR(z, myabs) + const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r)) const denom = dep.conservativeSqrt(denomsq) return dep.divideReal(num, denom) } -- 2.34.1 From 6492ffda90a741bb4424528782298891433712ef Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 21 Jan 2023 20:25:55 -0500 Subject: [PATCH 20/20] fix: typo in .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index df7c548..d0c9d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -129,7 +129,7 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test -# Webstiorm +# Webstorm .idea # yarn v2 -- 2.34.1