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.
This commit is contained in:
Glen Whitney 2022-12-27 17:55:17 -05:00
parent 8c06c8f36e
commit 7db6f38a30
17 changed files with 170 additions and 285 deletions

View File

@ -1,10 +1,10 @@
import {ForType} from '../core/Dispatcher.js' import {ForType} from '../core/Dispatcher.js'
import {ComplexReturn} from './type.js' import {ComplexImpTypes} from './type.js'
import * as Complex from './native.js' import * as Complex from './native.js'
export {Complex} export {Complex}
declare module "../core/Dispatcher" { declare module "../core/Dispatcher" {
interface ReturnTypes<Params> interface ImpTypes<T>
extends ForType<'Complex', ComplexReturn<Params>> {} extends ForType<'Complex', ComplexImpTypes<T>> {}
} }

View File

@ -1,33 +1,31 @@
import {Complex, UnderlyingReal, complex_binary} from './type.js' import {Complex, UnderlyingReal, complex_binary} from './type.js'
import { import {Dependency, ImpType} from '../core/Dispatcher.js'
BBinary, Dependency, ConservativeUnary, ConservativeBinary, ImpType
} from '../core/Dispatcher.js'
type ComplexUnary<T> =
T extends Complex<infer R> ? (a: Complex<R>) => Complex<R> : never
type ComplexBinary<T> =
T extends Complex<infer R>
? (a: Complex<R>, b: Complex<R>) => Complex<R>
: never
type ComplexReal<T> = T extends Complex<infer R>
? (a: Complex<R>, b: UnderlyingReal<R>) => Complex<R>
: never
declare module "./type" { declare module "./type" {
interface ComplexReturn<Params> { interface ComplexImpTypes<T> {
add: ConservativeBinary<Params, Complex<any>> add: ComplexBinary<T>
addReal: Params extends [infer Z, infer R] add_real: ComplexReal<T>
? [R] extends [UnderlyingReal<Z>] ? Z : never unaryMinus: ComplexUnary<T>
: never conj: ComplexUnary<T>
unaryMinus: ConservativeUnary<Params, Complex<any>> subtract: ComplexBinary<T>
conj: ConservativeUnary<Params, Complex<any>> multiply: ComplexBinary<T>
subtract: ConservativeBinary<Params, Complex<any>> absquare: T extends Complex<infer R> ? (a: T) => UnderlyingReal<R> : never
multiply: ConservativeBinary<Params, Complex<any>> reciprocal: ComplexUnary<T>
absquare: Params extends [infer Z] divide: ComplexBinary<T>
? Z extends Complex<any> ? UnderlyingReal<Z> : never divide_real: ComplexReal<T>
: never
reciprocal: ConservativeUnary<Params, Complex<any>>
divide: ConservativeBinary<Params, Complex<any>>
divideByReal: Params extends [infer Z, infer R]
? [R] extends [UnderlyingReal<Z>] ? Z : never
: never
// square root that remains the same type // square root that remains the same type
conservativeSqrt: ConservativeUnary<Params, Complex<any>> conservativeSqrt: ComplexUnary<T>
// Same as conservativeSqrt for complex numbers: // Same as conservativeSqrt for complex numbers:
sqrt: ConservativeUnary<Params, Complex<any>> sqrt: ComplexUnary<T>
// complex square root of the real type of a complex:
complexSqrt: Params extends [infer T] ? Complex<T> : never
} }
} }
@ -36,10 +34,10 @@ export const add =
ImpType<'add', [Complex<T>, Complex<T>]> => ImpType<'add', [Complex<T>, Complex<T>]> =>
(w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im)) (w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im))
export const addReal = export const add_real =
<T>(dep: Dependency<'addReal', [T, UnderlyingReal<T>]>): <T>(dep: Dependency<'add_real', [T, UnderlyingReal<T>]>):
ImpType<'addReal', [Complex<T>, UnderlyingReal<T>]> => ImpType<'add_real', [Complex<T>, UnderlyingReal<T>]> =>
(z, r) => complex_binary(dep.addReal(z.re, r), z.im) (z, r) => complex_binary(dep.add_real(z.re, r), z.im)
export const unaryMinus = export const unaryMinus =
<T>(dep: Dependency<'unaryMinus', [T]>): <T>(dep: Dependency<'unaryMinus', [T]>):
@ -73,22 +71,22 @@ export const multiply =
export const absquare = export const absquare =
<T>(dep: Dependency<'absquare', [T]> <T>(dep: Dependency<'absquare', [T]>
& Dependency<'add', BBinary<UnderlyingReal<T>>>): & Dependency<'add', [UnderlyingReal<T>, UnderlyingReal<T>]>):
ImpType<'absquare', [Complex<T>]> => ImpType<'absquare', [Complex<T>]> =>
z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
export const divideByReal = export const divide_real =
<T>(dep: Dependency<'divideByReal', [T, UnderlyingReal<T>]>): <T>(dep: Dependency<'divide_real', [T, UnderlyingReal<T>]>):
ImpType<'divideByReal', [Complex<T>, UnderlyingReal<T>]> => ImpType<'divide_real', [Complex<T>, UnderlyingReal<T>]> =>
(z, r) => complex_binary( (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 = export const reciprocal =
<T>(dep: Dependency<'conj', [Complex<T>]> <T>(dep: Dependency<'conj', [Complex<T>]>
& Dependency<'absquare', [Complex<T>]> & Dependency<'absquare', [Complex<T>]>
& Dependency<'divideByReal', [Complex<T>, UnderlyingReal<T>]>): & Dependency<'divide_real', [Complex<T>, UnderlyingReal<T>]>):
ImpType<'reciprocal', [Complex<T>]> => ImpType<'reciprocal', [Complex<T>]> =>
z => dep.divideByReal(dep.conj(z), dep.absquare(z)) z => dep.divide_real(dep.conj(z), dep.absquare(z))
export const divide = export const divide =
<T>(dep: Dependency<'multiply', [Complex<T>, Complex<T>]> <T>(dep: Dependency<'multiply', [Complex<T>, Complex<T>]>
@ -96,44 +94,31 @@ export const divide =
ImpType<'divide', [Complex<T>, Complex<T>]> => ImpType<'divide', [Complex<T>, Complex<T>]> =>
(w, z) => dep.multiply(w, dep.reciprocal(z)) (w, z) => dep.multiply(w, dep.reciprocal(z))
export const complexSqrt =
<T>(dep: Dependency<'conservativeSqrt', [T]>
& Dependency<'isSquare', [T]>
& Dependency<'complex', [T]>
& Dependency<'unaryMinus', [T]>
& Dependency<'zero', [T]>
& Dependency<'nan', [Complex<T>]>): 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 = export const sqrt =
<T>(dep: Dependency<'isReal', [Complex<T>]> <T>(dep: Dependency<'absquare' | 're', [Complex<T>]>
& Dependency<'complexSqrt', [T]> & Dependency<'conservativeSqrt' | 'unaryMinus', [UnderlyingReal<T>]>
& Dependency<'absquare', [Complex<T>]> & Dependency<'divide_real', [Complex<T>, UnderlyingReal<T>]>
& Dependency<'conservativeSqrt', [UnderlyingReal<T>]> & Dependency<'add_real', [T, UnderlyingReal<T>]>
& Dependency<'addReal', [Complex<T>,UnderlyingReal<T>]> & {add_complex_real:
& Dependency<'re', [Complex<T>]> ImpType<'add_real', [Complex<T>, UnderlyingReal<T>]>}
& Dependency<'add', [UnderlyingReal<T>,UnderlyingReal<T>]> & Dependency<'equal' | 'add', [UnderlyingReal<T>, UnderlyingReal<T>]>
& Dependency<'divideByReal', [Complex<T>,UnderlyingReal<T>]> & Dependency<'complex', [T, T]>
& Dependency<'zero', [T]>
): ImpType<'sqrt', [Complex<T>]> => ): ImpType<'sqrt', [Complex<T>]> =>
z => { z => {
if (dep.isReal(z)) return dep.complexSqrt(z.re)
const myabs = dep.conservativeSqrt(dep.absquare(z)) const myabs = dep.conservativeSqrt(dep.absquare(z))
const num = dep.addReal(z, myabs)
const r = dep.re(z) 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 denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r))
const denom = dep.conservativeSqrt(denomsq) const denom = dep.conservativeSqrt(denomsq)
return dep.divideByReal(num, denom) return dep.divide_real(num, denom)
} }
export const conservativeSqrt = sqrt export const conservativeSqrt = sqrt

View File

@ -1 +1,4 @@
export * from './type.js' export * from './type.js'
export * from './arithmetic.js'
export * from './predicate.js'
export * from './relational.js'

View File

@ -1,10 +1,12 @@
import {Complex} from './type.js' import {Complex} from './type.js'
import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' import {Dependency, ImpType} from '../core/Dispatcher.js'
type ComplexPredicate<T> = T extends Complex<any> ? (a: T) => boolean : never
declare module "./type" { declare module "./type" {
interface ComplexReturn<Params> { interface ComplexImpTypes<T> {
isReal: Signature<Params, [Complex<any>], boolean> isReal: ComplexPredicate<T>
isSquare: Signature<Params, [Complex<any>], boolean> isSquare: ComplexPredicate<T>
} }
} }

View File

@ -1,11 +1,12 @@
import {Complex} from './type.js' import {Complex} from './type.js'
import {BBinary, ImpType, Dependency} from '../core/Dispatcher.js' import {ImpType, Dependency} from '../core/Dispatcher.js'
type ComplexRelation<T> =
T extends Complex<any> ? (a: T, b: T) => boolean : never
declare module "./type" { declare module "./type" {
interface ComplexReturn<Params> { interface ComplexImpTypes<T> {
equal: Params extends BBinary<infer B> equal: ComplexRelation<T>
? B extends Complex<any> ? boolean : never
: never
} }
} }

View File

@ -1,5 +1,5 @@
import { import {
joinTypes, typeOfDependency, Dependency, BBinary, ImpType, ImpReturns joinTypes, typeOfDependency, Dependency, ImpType, ImpReturns
} from '../core/Dispatcher.js' } from '../core/Dispatcher.js'
export type Complex<T> = {re: T; im: T;} export type Complex<T> = {re: T; im: T;}
@ -22,45 +22,16 @@ export const Complex_type = {
} }
} }
export interface ComplexReturn<Params> { export interface ComplexImpTypes<T> {
// Sadly, I can't think of a way to make some nice abbreviation operators complex: (a: T, b?: T) => Complex<T>
// for these generic type specifications because TypeScript generics zero: T extends Complex<infer R>
// can't take and use generic parameters, only fully instantiated types. ? (a: Complex<R>) => Complex<R | ImpReturns<'zero', [R]>> : never
complex: Params extends [infer U] ? Complex<U> // unary case one: T extends Complex<infer R>
: Params extends BBinary<infer B> ? Complex<B> // binary case ? (a: Complex<R>) => Complex<R | ImpReturns<'one' | 'zero', [R]>> : never
: never nan: T extends Complex<infer R>
? (a: Complex<R>) => Complex<R | ImpReturns<'NaN', [R]>> : never
// alternatively if it seems better; each definition is simpler, but at re: T extends Complex<infer R>
// the cost of having two keys here: ? (a: Complex<R>) => UnderlyingReal<R> : never
// complex_unary: Params extends [infer R] ? Complex<R> : never
// complex_binary: Params extends BBinary<infer R> ? Complex<R> : 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<R> : 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<infer T> // 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<infer T> // 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<infer T> // of a Complex parameter
? ImpReturns<'nan', T> extends T ? Z : never // has real NaN
: never
: never
re: Params extends [infer Z]
? Z extends Complex<infer T> ? UnderlyingReal<T> : never
: never
} }
export const complex_unary = export const complex_unary =

View File

@ -15,22 +15,22 @@ type DependenciesType = Record<string, Function>
export type typeOfDependency = {typeOf: (x: unknown) => TypeName} export type typeOfDependency = {typeOf: (x: unknown) => TypeName}
// All of the implementations must publish descriptions of their // 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: // described just below:
export interface ReturnTypes<Params> {} export interface ImpTypes<T> {}
/***** /*****
To describe one implementation for a hypothetical operation `foo`, there To describe one implementation for a hypothetical operation `foo`, there
should be a property of the interface whose name starts with `foo` and whose 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 next character, if any, is an underscore. The type of this property
must be the return type of that implementation when Params matches the must be the type of that implementation when T matches the
parameter types of the implementation, and `never` otherwise. first parameter of the implementation.
Thus to describe an implementation that takes a number and a string and Thus to describe an implementation that takes a number and a string and
returns a boolean, for example, you could write returns a boolean, for example, you could write
``` ```
declare module "Dispatcher" { declare module "Dispatcher" {
interface ReturnTypes<Params> { interface ImpTypes<T> {
foo_example: Params extends [number, string] ? boolean : never foo_example: (a: number, b: string) => boolean
} }
} }
``` ```
@ -38,52 +38,26 @@ export interface ReturnTypes<Params> {}
of any type and returns a Vector of that type, you can say of any type and returns a Vector of that type, you can say
``` ```
... ...
foo_generic: Params extends [infer T] ? Vector<T> : never foo_generic: (a: T) => Vector<T>
... ...
``` ```
In practice, each subdirectory corresponding to a type, like Complex, In practice, each subdirectory corresponding to a type, like Complex,
defines an interface, like `ComplexReturn<Params>` for the implementations defines an interface, like `ComplexImpTypes<T>` for the implementations
in that subdirectory, which can mostly be defined without suffixes because in that subdirectory, which can mostly be defined without suffixes because
there's typically just a single implementation within that domain. there's typically just a single implementation within that domain.
Then the module responsible for collating all of the implementations for Then the module responsible for collating all of the implementations for
that type inserts all of the properties of that interface into `ReturnTypes` that type inserts all of the properties of that interface into `ReturnTypes`
suitably suffixed to avoid collisions. suitably suffixed to avoid collisions.
One might think that simply defining an implementation for `foo` Note that if the type is not generic, it will not bother with the generic
of type `(n: number, s: string) => boolean` would provide all of the same parameter in its subdirectory ImpTypes interface.
information as the type of the key `foo_example` in the ReturnTypes
interface above, but in practice TypeScript has challenges in extracting And note again, that for generic types, the type parameter of ImpTypes
types relating to functions. (In particular, there is no _must_ match the type of the **first** argument of the operation. Hence,
way to get the specialized return type of a generic function when it is choose argument order so that you can infer all the other parameter types
called on aguments whose specific types match the generic parameters.) and the return type from the type of the first argument.
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, ActualParams, Returns> =
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<infer B> ? B : never`
// says that this implementation takes two arguments, both of type B, and
// returns the same type.
export type BBinary<B> = [B, B]
// A unary signature that preserves the type of its argument, which must
// extend the given Bound:
export type ConservativeUnary<CandidateParams, Bound> =
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, Bound> =
CandidateParams extends BBinary<infer B>
? B extends Bound ? B : never
: never
// Helper for collecting return types // Helper for collecting return types
// (Really just adds the literal string Suffix onto the keys of interface IFace) // (Really just adds the literal string Suffix onto the keys of interface IFace)
export type ForType<Suffix extends string, IFace> = keyof IFace extends string export type ForType<Suffix extends string, IFace> = 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 // Used to filter keys that match a given operation name
type BeginsWith<Name extends string> = Name | `${Name}_${string}` type BeginsWith<Name extends string> = Name | `${Name}_${string}`
// Look up the return type of an implementation based on its name export type RawDependency<Name extends string, Params extends unknown[]> =
// and the parameters it takes {[K in keyof ImpTypes<Params[0]>]: K extends BeginsWith<Name>
export type ImpReturns<Name extends string, Params> = ? ImpTypes<Params[0]>[K] extends (...args: Params) => any
{[K in keyof ReturnTypes<Params>]: K extends BeginsWith<Name> ? ImpTypes<Params[0]>[K]
? ReturnTypes<Params>[K] : never}[keyof ReturnTypes<Params>] : never
: never}
// The type of an implementation (with dependencies satisfied, // The type of an implementation (with dependencies satisfied,
// based on its name and the parameters it takes // based on its name and the parameters it takes
export type ImpType<Name extends string, Params extends unknown[]> = export type ImpType<Name extends string, Params extends unknown[]> =
(...args: Params) => ImpReturns<Name, Params> RawDependency<Name, Params>[keyof ImpTypes<Params[0]>]
// The type of a dependency on an implementation based on its name // The type of a dependency on an implementation based on its name
// and the parameters it takes (just a simple object with one property // and the parameters it takes (just a simple object with one property
@ -118,6 +93,12 @@ export type ImpType<Name extends string, Params extends unknown[]> =
export type Dependency<Name extends string, Params extends unknown[]> = export type Dependency<Name extends string, Params extends unknown[]> =
{[N in Name]: ImpType<N, Params>} {[N in Name]: ImpType<N, Params>}
// Look up the return type of an implementation based on its name
// and the parameters it takes
export type ImpReturns<Name extends string, Params extends unknown[]> =
ReturnType<ImpType<Name, Params>>
// Now types used in the Dispatcher class itself // Now types used in the Dispatcher class itself
type TypeSpecification = { type TypeSpecification = {

View File

@ -1,10 +1,10 @@
import { ForType } from '../core/Dispatcher.js' import { ForType } from '../core/Dispatcher.js'
import { GenericReturn } from './type.js' import { GenericImpTypes } from './type.js'
import * as generic from './arithmetic.js' import * as generic from './native.js'
export { generic } export { generic }
declare module "../core/Dispatcher" { declare module "../core/Dispatcher" {
interface ReturnTypes<Params> interface ImpTypes<T>
extends ForType<'generic', GenericReturn<Params>> { } extends ForType<'generic', GenericImpTypes<T>> { }
} }

View File

@ -1,39 +1,12 @@
import {Dependency, ImpType, ImpReturns} from "../core/Dispatcher"; import {Dependency, ImpType, ImpReturns} from '../core/Dispatcher.js'
declare module "./type" { declare module "./type" {
interface GenericReturn<Params> { interface GenericImpTypes<T> {
// Jos: not sure how to define this or why it is needed square: (a: T) => ImpReturns<'multiply', [T, T]>
// square: Signature<Params, [T], T>
// square: ConservativeUnary<Params, T>
// square: Params extends [infer R]
// ? R extends number ? UnderlyingReal<R> : 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 = export const square =
<T>(dep: Dependency<'multiply', [T, T]>): <T>(dep: Dependency<'multiply', [T, T]>):
ImpType<'square', [T]> => ImpType<'square', [T]> =>
z => dep.multiply(z, z) t => dep.multiply(t, t)

View File

@ -1,3 +1,3 @@
export interface GenericReturn<Params> { export interface GenericImpTypes<T> {
} }

View File

@ -2,3 +2,19 @@ import {Dispatcher} from './core/Dispatcher.js'
import * as Specifications from './all.js' import * as Specifications from './all.js'
export default new Dispatcher(Specifications) 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<number>) => 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)

View File

@ -1,10 +1,10 @@
import {ForType} from '../core/Dispatcher.js' import {ForType} from '../core/Dispatcher.js'
import {NumbersReturn} from './type.js' import {NumbersImpTypes} from './type.js'
import * as numbers from './native.js' import * as numbers from './native.js'
export {numbers} export {numbers}
declare module "../core/Dispatcher" { declare module "../core/Dispatcher" {
interface ReturnTypes<Params> interface ImpTypes<T>
extends ForType<'numbers', NumbersReturn<Params>> {} extends ForType<'numbers', NumbersImpTypes> {}
} }

View File

@ -1,53 +1,32 @@
import {configDependency} from '../core/Config.js' import {configDependency} from '../core/Config.js'
import { import {
Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType Dependency, ImpType
} from '../core/Dispatcher.js' } from '../core/Dispatcher.js'
import type {Complex, UnderlyingReal} from '../Complex/type.js' import type {Complex, UnderlyingReal} from '../Complex/type.js'
type UnaryNumber = (a: number) => number
type BinaryNumber = (a: number, b:number) => number
declare module "./type" { declare module "./type" {
interface NumbersReturn<Params> { interface NumbersImpTypes {
// This description loses information: some subtypes like NumInt or add: BinaryNumber
// Positive are closed under addition, but this says that the result unaryMinus: UnaryNumber
// of add is just a number, not still of the reduced type conj: UnaryNumber
// add: Signature<Params, [number, number], number> subtract: BinaryNumber
multiply: BinaryNumber
// Whereas this one preserves information, but lies absquare: UnaryNumber
// because it claims all subtypes of number are closed under addition, reciprocal: UnaryNumber
// which is not true for `1 | 2 | 3`, for example. But because in divide: BinaryNumber
// generics that use add we often need to assign the result of add conservativeSqrt: UnaryNumber
// 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<Params, number>
// 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<Params, number>
conj: ConservativeUnary<Params, number>
subtract: ConservativeBinary<Params, number>
multiply: ConservativeBinary<Params, number>
absquare: Params extends [infer R]
? R extends number ? UnderlyingReal<R> : never
: never
reciprocal: ConservativeUnary<Params, number>
divide: ConservativeBinary<Params, number>
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<Params, number>
// Best we can do for sqrt at compile time, since actual return // Best we can do for sqrt at compile time, since actual return
// type depends on config. Not sure how this will play out // type depends on config. Not sure how this will play out
// when we make a number-only bundle, but at least the import type // when we make a number-only bundle, but at least the import type
// above for Complex<> does not lead to any emitted JavaScript. // above for Complex<> does not lead to any emitted JavaScript.
sqrt: Signature<Params, [number], number | Complex<number>> sqrt: (a: number) => number | Complex<number>
} }
} }
export const add: ImpType<'add', [number, number]> = (a, b) => a + b 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 unaryMinus: ImpType<'unaryMinus', [number]> = a => -a
export const conj: ImpType<'conj', [number]> = a => a export const conj: ImpType<'conj', [number]> = a => a
export const subtract: ImpType<'subtract', [number, number]> = (a, b) => a - b 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 absquare: ImpType<'absquare', [number]> = a => a*a
export const reciprocal: ImpType<'reciprocal', [number]> = a => 1/a export const reciprocal: ImpType<'reciprocal', [number]> = a => 1/a
export const divide: ImpType<'divide', [number, number]> = (a, b) => a / b 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]> = export const conservativeSqrt: ImpType<'conservativeSqrt', [number]> =
a => isNaN(a) ? NaN : Math.sqrt(a) a => isNaN(a) ? NaN : Math.sqrt(a)

View File

@ -1,2 +1,4 @@
export * from './type.js' export * from './type.js'
export * from './arithmetic.js' export * from './arithmetic.js'
export * from './predicate.js'
export * from './relational.js'

View File

@ -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" { declare module "./type" {
interface NumbersReturn<Params> { interface NumbersImpTypes {
isReal: Signature<Params, [number], true> isReal: NumberPredicate
isSquare: Signature<Params, [number], boolean> isSquare: NumberPredicate
} }
} }

View File

@ -1,12 +1,13 @@
import {configDependency} from '../core/Config.js' 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 const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16
type NumberRelation = (a: number, b: number) => boolean
declare module "./type" { declare module "./type" {
interface NumbersReturn<Params> { interface NumbersImpTypes {
equal: Signature<Params, [number, number], boolean> equal: NumberRelation
unequal: Signature<Params, [number, number], boolean>
} }
} }
@ -26,9 +27,3 @@ export const equal =
return false return false
} }
export const unequal = (dep: Dependency<'equal', [number, number]>):
ImpType<'unequal', [number, number]> =>
(x, y) => {
return !dep.equal(x, y)
}

View File

@ -8,34 +8,11 @@ export const number_type = {
} }
export interface NumbersReturn<Params> { export interface NumbersImpTypes {
// The following description of the return type of `zero` on a single zero: (a: number) => 0
// number argument has ended up unfortunately rather complicated. However, one: (a: number) => 1
// it illustrates the typing is really working: Suppose we have a nan: (a: number) => typeof NaN
// `type Small = 1 | 2 | 3`. Then Small indeed extends number, but we re: (a: number) => number
// 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<Params, [number], 0>
// makes complex fail to compile, because it worries that you might be
// making `Complex<Small>` 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<T> : never
: never
} }
export const zero: ImpType<'zero', [number]> = a => 0 export const zero: ImpType<'zero', [number]> = a => 0