From 7db6f38a30a1d4d67e1f93088f6b57c10f6fdda7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Tue, 27 Dec 2022 17:55:17 -0500 Subject: [PATCH] refactor: Specify implementation types directly Rather than speficying return types as a type transformation from parameter types, assume that all the type info can be inferred from the first parameter, and directly specify the implementation types. Vastly simplifies the declaration of implementation types. --- src/Complex/all.ts | 6 +- src/Complex/arithmetic.ts | 119 +++++++++++++++++--------------------- src/Complex/native.ts | 3 + src/Complex/predicate.ts | 10 ++-- src/Complex/relational.ts | 11 ++-- src/Complex/type.ts | 51 ++++------------ src/core/Dispatcher.ts | 75 +++++++++--------------- src/generic/all.ts | 8 +-- src/generic/arithmetic.ts | 35 ++--------- src/generic/type.ts | 4 +- src/index.ts | 16 +++++ src/numbers/all.ts | 6 +- src/numbers/arithmetic.ts | 52 +++++------------ src/numbers/native.ts | 2 + src/numbers/predicate.ts | 9 +-- src/numbers/relational.ts | 15 ++--- src/numbers/type.ts | 33 ++--------- 17 files changed, 170 insertions(+), 285 deletions(-) diff --git a/src/Complex/all.ts b/src/Complex/all.ts index f5369ec..b395743 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,10 +1,10 @@ import {ForType} from '../core/Dispatcher.js' -import {ComplexReturn} from './type.js' +import {ComplexImpTypes} from './type.js' import * as Complex from './native.js' export {Complex} declare module "../core/Dispatcher" { - interface ReturnTypes - extends ForType<'Complex', ComplexReturn> {} + interface ImpTypes + extends ForType<'Complex', ComplexImpTypes> {} } diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts index 8fc32fb..320cbdc 100644 --- a/src/Complex/arithmetic.ts +++ b/src/Complex/arithmetic.ts @@ -1,33 +1,31 @@ import {Complex, UnderlyingReal, complex_binary} from './type.js' -import { - BBinary, Dependency, ConservativeUnary, ConservativeBinary, ImpType -} from '../core/Dispatcher.js' +import {Dependency, ImpType} from '../core/Dispatcher.js' +type ComplexUnary = + T extends Complex ? (a: Complex) => Complex : never +type ComplexBinary = + T extends Complex + ? (a: Complex, b: Complex) => Complex + : never +type ComplexReal = T extends Complex + ? (a: Complex, b: UnderlyingReal) => Complex + : never 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 + interface ComplexImpTypes { + add: ComplexBinary + add_real: ComplexReal + unaryMinus: ComplexUnary + conj: ComplexUnary + subtract: ComplexBinary + multiply: ComplexBinary + absquare: T extends Complex ? (a: T) => UnderlyingReal : never + reciprocal: ComplexUnary + divide: ComplexBinary + divide_real: ComplexReal // square root that remains the same type - conservativeSqrt: ConservativeUnary> + conservativeSqrt: ComplexUnary // 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 + sqrt: ComplexUnary } } @@ -36,10 +34,10 @@ export const add = 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 add_real = + (dep: Dependency<'add_real', [T, UnderlyingReal]>): + ImpType<'add_real', [Complex, UnderlyingReal]> => + (z, r) => complex_binary(dep.add_real(z.re, r), z.im) export const unaryMinus = (dep: Dependency<'unaryMinus', [T]>): @@ -73,22 +71,22 @@ export const multiply = export const absquare = (dep: Dependency<'absquare', [T]> - & Dependency<'add', BBinary>>): + & Dependency<'add', [UnderlyingReal, UnderlyingReal]>): 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]> => +export const divide_real = + (dep: Dependency<'divide_real', [T, UnderlyingReal]>): + ImpType<'divide_real', [Complex, UnderlyingReal]> => (z, r) => complex_binary( - dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + dep.divide_real(z.re, r), dep.divide_real(z.im, r)) export const reciprocal = (dep: Dependency<'conj', [Complex]> & Dependency<'absquare', [Complex]> - & Dependency<'divideByReal', [Complex, UnderlyingReal]>): + & Dependency<'divide_real', [Complex, UnderlyingReal]>): ImpType<'reciprocal', [Complex]> => - z => dep.divideByReal(dep.conj(z), dep.absquare(z)) + z => dep.divide_real(dep.conj(z), dep.absquare(z)) export const divide = (dep: Dependency<'multiply', [Complex, Complex]> @@ -96,44 +94,31 @@ export const divide = 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]> + (dep: Dependency<'absquare' | 're', [Complex]> + & Dependency<'conservativeSqrt' | 'unaryMinus', [UnderlyingReal]> + & Dependency<'divide_real', [Complex, UnderlyingReal]> + & Dependency<'add_real', [T, UnderlyingReal]> + & {add_complex_real: + ImpType<'add_real', [Complex, UnderlyingReal]>} + & Dependency<'equal' | 'add', [UnderlyingReal, UnderlyingReal]> + & Dependency<'complex', [T, T]> + & Dependency<'zero', [T]> ): 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 negr = dep.unaryMinus(r) + if (dep.equal(myabs, negr)) { + // pure imaginary square root; z.im already zero + return dep.complex( + dep.zero(z.re), dep.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/native.ts b/src/Complex/native.ts index 6a91ee7..ea2f66b 100644 --- a/src/Complex/native.ts +++ b/src/Complex/native.ts @@ -1 +1,4 @@ export * from './type.js' +export * from './arithmetic.js' +export * from './predicate.js' +export * from './relational.js' diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index eafc5ad..c4b204e 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,10 +1,12 @@ import {Complex} from './type.js' -import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' +import {Dependency, ImpType} from '../core/Dispatcher.js' + +type ComplexPredicate = T extends Complex ? (a: T) => boolean : never declare module "./type" { - interface ComplexReturn { - isReal: Signature], boolean> - isSquare: Signature], boolean> + interface ComplexImpTypes { + isReal: ComplexPredicate + isSquare: ComplexPredicate } } diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts index 2a57dc4..ac2d1b4 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,11 +1,12 @@ import {Complex} from './type.js' -import {BBinary, ImpType, Dependency} from '../core/Dispatcher.js' +import {ImpType, Dependency} from '../core/Dispatcher.js' + +type ComplexRelation = + T extends Complex ? (a: T, b: T) => boolean : never declare module "./type" { - interface ComplexReturn { - equal: Params extends BBinary - ? B extends Complex ? boolean : never - : never + interface ComplexImpTypes { + equal: ComplexRelation } } diff --git a/src/Complex/type.ts b/src/Complex/type.ts index 36c040a..d8ccac2 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,5 +1,5 @@ import { - joinTypes, typeOfDependency, Dependency, BBinary, ImpType, ImpReturns + joinTypes, typeOfDependency, Dependency, ImpType, ImpReturns } from '../core/Dispatcher.js' export type Complex = {re: T; im: T;} @@ -22,45 +22,16 @@ export const Complex_type = { } } -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 interface ComplexImpTypes { + complex: (a: T, b?: T) => Complex + zero: T extends Complex + ? (a: Complex) => Complex> : never + one: T extends Complex + ? (a: Complex) => Complex> : never + nan: T extends Complex + ? (a: Complex) => Complex> : never + re: T extends Complex + ? (a: Complex) => UnderlyingReal : never } export const complex_unary = diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index aa4c6a9..c1167f1 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -15,22 +15,22 @@ type DependenciesType = Record 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 +// types into the following interface, using the format // described just below: -export interface ReturnTypes {} +export interface ImpTypes {} /***** 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. + must be the type of that implementation when T matches the + first parameter of the implementation. 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 + interface ImpTypes { + foo_example: (a: number, b: string) => boolean } } ``` @@ -38,52 +38,26 @@ export interface ReturnTypes {} of any type and returns a Vector of that type, you can say ``` ... - foo_generic: Params extends [infer T] ? Vector : never + foo_generic: (a: T) => Vector ... ``` In practice, each subdirectory corresponding to a type, like Complex, - defines an interface, like `ComplexReturn` for the implementations + defines an interface, like `ComplexImpTypes` 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. + Note that if the type is not generic, it will not bother with the generic + parameter in its subdirectory ImpTypes interface. + + And note again, that for generic types, the type parameter of ImpTypes + _must_ match the type of the **first** argument of the operation. Hence, + choose argument order so that you can infer all the other parameter types + and the return type from the type of the first argument. *****/ -// Helpers for specifying signatures - -// A basic signature with concrete types -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 @@ -99,16 +73,17 @@ export function joinTypes(a: TypeName, b: TypeName) { // 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] +export type RawDependency = + {[K in keyof ImpTypes]: K extends BeginsWith + ? ImpTypes[K] extends (...args: Params) => any + ? ImpTypes[K] + : 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 + RawDependency[keyof ImpTypes] // The type of a dependency on an implementation based on its name // and the parameters it takes (just a simple object with one property @@ -118,6 +93,12 @@ export type ImpType = export type Dependency = {[N in Name]: ImpType} +// Look up the return type of an implementation based on its name +// and the parameters it takes +export type ImpReturns = + ReturnType> + + // Now types used in the Dispatcher class itself type TypeSpecification = { diff --git a/src/generic/all.ts b/src/generic/all.ts index 1a008ac..7548c94 100644 --- a/src/generic/all.ts +++ b/src/generic/all.ts @@ -1,10 +1,10 @@ import { ForType } from '../core/Dispatcher.js' -import { GenericReturn } from './type.js' -import * as generic from './arithmetic.js' +import { GenericImpTypes } from './type.js' +import * as generic from './native.js' export { generic } declare module "../core/Dispatcher" { - interface ReturnTypes - extends ForType<'generic', GenericReturn> { } + interface ImpTypes + extends ForType<'generic', GenericImpTypes> { } } diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts index 46d6922..cc44deb 100644 --- a/src/generic/arithmetic.ts +++ b/src/generic/arithmetic.ts @@ -1,39 +1,12 @@ -import {Dependency, ImpType, ImpReturns} from "../core/Dispatcher"; +import {Dependency, ImpType, ImpReturns} from '../core/Dispatcher.js' 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). + interface GenericImpTypes { + square: (a: T) => ImpReturns<'multiply', [T, T]> } } export const square = (dep: Dependency<'multiply', [T, T]>): ImpType<'square', [T]> => - z => dep.multiply(z, z) + t => dep.multiply(t, t) diff --git a/src/generic/type.ts b/src/generic/type.ts index 8589417..7b7f208 100644 --- a/src/generic/type.ts +++ b/src/generic/type.ts @@ -1,3 +1,3 @@ -export interface GenericReturn { +export interface GenericImpTypes { -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index bb83486..297b271 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,19 @@ import {Dispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' export default new Dispatcher(Specifications) + +import {Complex} from './Complex/type.js' +import {absquare as absquare_complex} from './Complex/arithmetic.js' + +const mockRealAdd = (a: number, b: number) => a+b +const mockComplexAbsquare = (z: Complex) => z.re*z.re + z.im*z.im + +const quatAbsquare = absquare_complex({ + add: mockRealAdd, + absquare: mockComplexAbsquare +}) + +const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) +const typeTest: typeof myabs = 7 // check myabs is just a number + +console.log('Result is', myabs) diff --git a/src/numbers/all.ts b/src/numbers/all.ts index b034f25..74fb578 100644 --- a/src/numbers/all.ts +++ b/src/numbers/all.ts @@ -1,10 +1,10 @@ import {ForType} from '../core/Dispatcher.js' -import {NumbersReturn} from './type.js' +import {NumbersImpTypes} from './type.js' import * as numbers from './native.js' export {numbers} declare module "../core/Dispatcher" { - interface ReturnTypes - extends ForType<'numbers', NumbersReturn> {} + interface ImpTypes + extends ForType<'numbers', NumbersImpTypes> {} } diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index b02b09f..a2b7fb1 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,53 +1,32 @@ import {configDependency} from '../core/Config.js' import { - Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType + Dependency, ImpType } from '../core/Dispatcher.js' import type {Complex, UnderlyingReal} from '../Complex/type.js' +type UnaryNumber = (a: number) => number +type BinaryNumber = (a: number, b:number) => number + 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 + interface NumbersImpTypes { + add: BinaryNumber + unaryMinus: UnaryNumber + conj: UnaryNumber + subtract: BinaryNumber + multiply: BinaryNumber + absquare: UnaryNumber + reciprocal: UnaryNumber + divide: BinaryNumber + conservativeSqrt: UnaryNumber // 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> + sqrt: (a: number) => number | Complex } } 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 @@ -55,7 +34,6 @@ 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) diff --git a/src/numbers/native.ts b/src/numbers/native.ts index 10cd111..ea2f66b 100644 --- a/src/numbers/native.ts +++ b/src/numbers/native.ts @@ -1,2 +1,4 @@ export * from './type.js' export * from './arithmetic.js' +export * from './predicate.js' +export * from './relational.js' diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index b8cc4c5..5d6ac84 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,9 +1,10 @@ -import {Signature, ImpType} from '../core/Dispatcher.js' +import {ImpType} from '../core/Dispatcher.js' +type NumberPredicate = (a: number) => boolean declare module "./type" { - interface NumbersReturn { - isReal: Signature - isSquare: Signature + interface NumbersImpTypes { + isReal: NumberPredicate + isSquare: NumberPredicate } } diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index 51d7e07..b7ce71a 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,12 +1,13 @@ import {configDependency} from '../core/Config.js' -import {Signature, ImpType, Dependency} from '../core/Dispatcher.js' +import {ImpType, Dependency} from '../core/Dispatcher.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 +type NumberRelation = (a: number, b: number) => boolean + declare module "./type" { - interface NumbersReturn { - equal: Signature - unequal: Signature + interface NumbersImpTypes { + equal: NumberRelation } } @@ -26,9 +27,3 @@ export const equal = return false } - -export const unequal = (dep: Dependency<'equal', [number, number]>): - ImpType<'unequal', [number, number]> => - (x, y) => { - return !dep.equal(x, y) - } diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 46971fc..a2db62d 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -8,34 +8,11 @@ export const number_type = { } -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 interface NumbersImpTypes { + zero: (a: number) => 0 + one: (a: number) => 1 + nan: (a: number) => typeof NaN + re: (a: number) => number } export const zero: ImpType<'zero', [number]> = a => 0