Compare commits
4 Commits
main
...
signature_
Author | SHA1 | Date | |
---|---|---|---|
8c06c8f36e | |||
fbec410c42 | |||
d55776655f | |||
1eb73be2fa |
@ -1,8 +1,10 @@
|
|||||||
import {ForType} from '../core/Dispatcher.js'
|
import {ForType} from '../core/Dispatcher.js'
|
||||||
|
import {ComplexReturn} 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 ImplementationTypes extends ForType<'Complex', typeof Complex> {}
|
interface ReturnTypes<Params>
|
||||||
|
extends ForType<'Complex', ComplexReturn<Params>> {}
|
||||||
}
|
}
|
||||||
|
139
src/Complex/arithmetic.ts
Normal file
139
src/Complex/arithmetic.ts
Normal file
@ -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<Params> {
|
||||||
|
add: ConservativeBinary<Params, Complex<any>>
|
||||||
|
addReal: Params extends [infer Z, infer R]
|
||||||
|
? [R] extends [UnderlyingReal<Z>] ? Z : never
|
||||||
|
: never
|
||||||
|
unaryMinus: ConservativeUnary<Params, Complex<any>>
|
||||||
|
conj: ConservativeUnary<Params, Complex<any>>
|
||||||
|
subtract: ConservativeBinary<Params, Complex<any>>
|
||||||
|
multiply: ConservativeBinary<Params, Complex<any>>
|
||||||
|
absquare: Params extends [infer Z]
|
||||||
|
? Z extends Complex<any> ? UnderlyingReal<Z> : never
|
||||||
|
: 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
|
||||||
|
conservativeSqrt: ConservativeUnary<Params, Complex<any>>
|
||||||
|
// Same as conservativeSqrt for complex numbers:
|
||||||
|
sqrt: ConservativeUnary<Params, Complex<any>>
|
||||||
|
|
||||||
|
// complex square root of the real type of a complex:
|
||||||
|
complexSqrt: Params extends [infer T] ? Complex<T> : never
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const add =
|
||||||
|
<T>(dep: Dependency<'add', [T,T]>):
|
||||||
|
ImpType<'add', [Complex<T>, Complex<T>]> =>
|
||||||
|
(w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im))
|
||||||
|
|
||||||
|
export const addReal =
|
||||||
|
<T>(dep: Dependency<'addReal', [T, UnderlyingReal<T>]>):
|
||||||
|
ImpType<'addReal', [Complex<T>, UnderlyingReal<T>]> =>
|
||||||
|
(z, r) => complex_binary(dep.addReal(z.re, r), z.im)
|
||||||
|
|
||||||
|
export const unaryMinus =
|
||||||
|
<T>(dep: Dependency<'unaryMinus', [T]>):
|
||||||
|
ImpType<'unaryMinus', [Complex<T>]> =>
|
||||||
|
z => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
|
||||||
|
|
||||||
|
export const conj =
|
||||||
|
<T>(dep: Dependency<'unaryMinus'|'conj', [T]>):
|
||||||
|
ImpType<'conj', [Complex<T>]> =>
|
||||||
|
z => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im))
|
||||||
|
|
||||||
|
export const subtract =
|
||||||
|
<T>(dep: Dependency<'subtract', [T,T]>):
|
||||||
|
ImpType<'subtract', [Complex<T>, Complex<T>]> =>
|
||||||
|
(w, z) => complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im))
|
||||||
|
|
||||||
|
export const multiply =
|
||||||
|
<T>(dep: Dependency<'add', [T,T]>
|
||||||
|
& Dependency<'subtract', [T,T]>
|
||||||
|
& Dependency<'multiply', [T,T]>
|
||||||
|
& Dependency<'conj', [T]>):
|
||||||
|
ImpType<'multiply', [Complex<T>, Complex<T>]> =>
|
||||||
|
(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 =
|
||||||
|
<T>(dep: Dependency<'absquare', [T]>
|
||||||
|
& Dependency<'add', BBinary<UnderlyingReal<T>>>):
|
||||||
|
ImpType<'absquare', [Complex<T>]> =>
|
||||||
|
z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
|
||||||
|
|
||||||
|
export const divideByReal =
|
||||||
|
<T>(dep: Dependency<'divideByReal', [T, UnderlyingReal<T>]>):
|
||||||
|
ImpType<'divideByReal', [Complex<T>, UnderlyingReal<T>]> =>
|
||||||
|
(z, r) => complex_binary(
|
||||||
|
dep.divideByReal(z.re, r), dep.divideByReal(z.im, r))
|
||||||
|
|
||||||
|
export const reciprocal =
|
||||||
|
<T>(dep: Dependency<'conj', [Complex<T>]>
|
||||||
|
& Dependency<'absquare', [Complex<T>]>
|
||||||
|
& Dependency<'divideByReal', [Complex<T>, UnderlyingReal<T>]>):
|
||||||
|
ImpType<'reciprocal', [Complex<T>]> =>
|
||||||
|
z => dep.divideByReal(dep.conj(z), dep.absquare(z))
|
||||||
|
|
||||||
|
export const divide =
|
||||||
|
<T>(dep: Dependency<'multiply', [Complex<T>, Complex<T>]>
|
||||||
|
& Dependency<'reciprocal', [Complex<T>]>):
|
||||||
|
ImpType<'divide', [Complex<T>, Complex<T>]> =>
|
||||||
|
(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 =
|
||||||
|
<T>(dep: Dependency<'isReal', [Complex<T>]>
|
||||||
|
& Dependency<'complexSqrt', [T]>
|
||||||
|
& Dependency<'absquare', [Complex<T>]>
|
||||||
|
& Dependency<'conservativeSqrt', [UnderlyingReal<T>]>
|
||||||
|
& Dependency<'addReal', [Complex<T>,UnderlyingReal<T>]>
|
||||||
|
& Dependency<'re', [Complex<T>]>
|
||||||
|
& Dependency<'add', [UnderlyingReal<T>,UnderlyingReal<T>]>
|
||||||
|
& Dependency<'divideByReal', [Complex<T>,UnderlyingReal<T>]>
|
||||||
|
): ImpType<'sqrt', [Complex<T>]> =>
|
||||||
|
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
|
19
src/Complex/predicate.ts
Normal file
19
src/Complex/predicate.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {Complex} from './type.js'
|
||||||
|
import {Signature, Dependency, ImpType} from '../core/Dispatcher.js'
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface ComplexReturn<Params> {
|
||||||
|
isReal: Signature<Params, [Complex<any>], boolean>
|
||||||
|
isSquare: Signature<Params, [Complex<any>], boolean>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isReal =
|
||||||
|
<T>(dep: Dependency<'equal', [T,T]>
|
||||||
|
& Dependency<'add', [T,T]>
|
||||||
|
& Dependency<'isReal', [T]>
|
||||||
|
): ImpType<'isReal', [Complex<T>]> =>
|
||||||
|
z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
|
||||||
|
|
||||||
|
export const isSquare: ImpType<'isSquare', [Complex<any>]> =
|
||||||
|
z => true // FIXME: not correct for Complex<bigint> once we get there
|
15
src/Complex/relational.ts
Normal file
15
src/Complex/relational.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {Complex} from './type.js'
|
||||||
|
import {BBinary, ImpType, Dependency} from '../core/Dispatcher.js'
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface ComplexReturn<Params> {
|
||||||
|
equal: Params extends BBinary<infer B>
|
||||||
|
? B extends Complex<any> ? boolean : never
|
||||||
|
: never
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const equal =
|
||||||
|
<T>(dep: Dependency<'equal', [T,T]>):
|
||||||
|
ImpType<'equal', [Complex<T>, Complex<T>]> =>
|
||||||
|
(w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im)
|
@ -1,7 +1,12 @@
|
|||||||
import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js'
|
import {
|
||||||
|
joinTypes, typeOfDependency, Dependency, BBinary, ImpType, ImpReturns
|
||||||
|
} from '../core/Dispatcher.js'
|
||||||
|
|
||||||
export type Complex<T> = {re: T; im: T;}
|
export type Complex<T> = {re: T; im: T;}
|
||||||
|
|
||||||
|
export type UnderlyingReal<T> =
|
||||||
|
T extends Complex<infer U> ? UnderlyingReal<U> : T
|
||||||
|
|
||||||
export const Complex_type = {
|
export const Complex_type = {
|
||||||
test: <T>(dep: {testT: (z: unknown) => z is T}) =>
|
test: <T>(dep: {testT: (z: unknown) => z is T}) =>
|
||||||
(z: unknown): z is Complex<T> =>
|
(z: unknown): z is Complex<T> =>
|
||||||
@ -17,6 +22,66 @@ export const Complex_type = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const complex_unary = <T>(dep: Dependency<'zero', [T]>) =>
|
export interface ComplexReturn<Params> {
|
||||||
(t: T) => ({re: t, im: dep.zero(t)})
|
// Sadly, I can't think of a way to make some nice abbreviation operators
|
||||||
export const complex_binary = <T>(t: T, u: T) => ({re: t, im: u})
|
// 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<U> // unary case
|
||||||
|
: Params extends BBinary<infer B> ? Complex<B> // 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<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 =
|
||||||
|
<T>(dep: Dependency<'zero', [T]>): ImpType<'complex', [T]> =>
|
||||||
|
t => ({re: t, im: dep.zero(t)})
|
||||||
|
export const complex_binary = <T>(t: T, u: T): ImpReturns<'complex', [T,T]> =>
|
||||||
|
({re: t, im: u})
|
||||||
|
|
||||||
|
export const zero =
|
||||||
|
<T>(dep: Dependency<'zero', [T]>): ImpType<'zero', [Complex<T>]> =>
|
||||||
|
z => complex_binary(dep.zero(z.re), dep.zero(z.im))
|
||||||
|
|
||||||
|
export const one =
|
||||||
|
<T>(dep: Dependency<'zero' | 'one', [T]>): ImpType<'one', [Complex<T>]> =>
|
||||||
|
z => // Must provide parameter T, else TS narrows to return type of dep.one
|
||||||
|
complex_binary<T>(dep.one(z.re), dep.zero(z.im))
|
||||||
|
|
||||||
|
export const nan =
|
||||||
|
<T>(dep: Dependency<'nan', [T]>): ImpType<'nan', [Complex<T>]> =>
|
||||||
|
z => complex_binary(dep.nan(z.re), dep.nan(z.im))
|
||||||
|
|
||||||
|
export const re =
|
||||||
|
<T>(dep: Dependency<'re', [T]>): ImpType<'re', [Complex<T>]> =>
|
||||||
|
z => dep.re(z.re)
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from './numbers/all.js'
|
export * from './numbers/all.js'
|
||||||
export * from './Complex/all.js'
|
export * from './Complex/all.js'
|
||||||
|
export * from './generic/all.js'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export type Config = {
|
export type Config = {
|
||||||
|
epsilon: number
|
||||||
predictable: boolean
|
predictable: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,15 +9,85 @@
|
|||||||
|
|
||||||
type TypeName = string
|
type TypeName = string
|
||||||
type Parameter = TypeName
|
type Parameter = TypeName
|
||||||
type Signature = Parameter[]
|
type InputSignature = Parameter[]
|
||||||
|
type DependenciesType = Record<string, Function>
|
||||||
|
|
||||||
export interface ImplementationTypes {}
|
|
||||||
export type typeOfDependency = {typeOf: (x: unknown) => TypeName}
|
export type typeOfDependency = {typeOf: (x: unknown) => TypeName}
|
||||||
|
|
||||||
// Helper for collecting implementations
|
// All of the implementations must publish descriptions of their
|
||||||
// (Really just suffixes the type name onto the keys of exports)
|
// return types into the following interface, using the format
|
||||||
export type ForType<T extends string, Exports> = keyof Exports extends string
|
// described just below:
|
||||||
? {[K in keyof Exports as `${K}_${T}`]: Exports[K]}
|
export interface ReturnTypes<Params> {}
|
||||||
|
|
||||||
|
/*****
|
||||||
|
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<Params> {
|
||||||
|
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<T> : never
|
||||||
|
...
|
||||||
|
```
|
||||||
|
In practice, each subdirectory corresponding to a type, like Complex,
|
||||||
|
defines an interface, like `ComplexReturn<Params>` 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, 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
|
||||||
|
// (Really just adds the literal string Suffix onto the keys of interface IFace)
|
||||||
|
export type ForType<Suffix extends string, IFace> = keyof IFace extends string
|
||||||
|
? {[K in keyof IFace as `${K}_${Suffix}`]: IFace[K]}
|
||||||
: never
|
: never
|
||||||
|
|
||||||
//dummy implementation for now
|
//dummy implementation for now
|
||||||
@ -26,27 +96,27 @@ export function joinTypes(a: TypeName, b: TypeName) {
|
|||||||
return 'any'
|
return 'any'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Used to filter keys that match a given operation name
|
||||||
* Build up to Dependency type lookup
|
type BeginsWith<Name extends string> = Name | `${Name}_${string}`
|
||||||
*/
|
|
||||||
type DependenciesType = Record<string, Function>
|
|
||||||
|
|
||||||
type FinalShape<FuncType> =
|
// Look up the return type of an implementation based on its name
|
||||||
FuncType extends (arg: DependenciesType) => Function
|
// and the parameters it takes
|
||||||
? ReturnType<FuncType> : FuncType
|
export type ImpReturns<Name extends string, Params> =
|
||||||
|
{[K in keyof ReturnTypes<Params>]: K extends BeginsWith<Name>
|
||||||
|
? ReturnTypes<Params>[K] : never}[keyof ReturnTypes<Params>]
|
||||||
|
|
||||||
type BeginsWith<Name extends string> = `${Name}${string}`
|
// The type of an implementation (with dependencies satisfied,
|
||||||
|
// based on its name and the parameters it takes
|
||||||
type DependencyTypes<Ob, Name extends string, Params extends unknown[]> =
|
export type ImpType<Name extends string, Params extends unknown[]> =
|
||||||
{[K in keyof Ob]: K extends BeginsWith<Name>
|
(...args: Params) => ImpReturns<Name, Params>
|
||||||
? FinalShape<Ob[K]> extends (...args: Params) => any
|
|
||||||
? FinalShape<Ob[K]>
|
|
||||||
: never
|
|
||||||
: never}
|
|
||||||
|
|
||||||
|
// 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<Name extends string, Params extends unknown[]> =
|
export type Dependency<Name extends string, Params extends unknown[]> =
|
||||||
{[N in Name]:
|
{[N in Name]: ImpType<N, Params>}
|
||||||
DependencyTypes<ImplementationTypes, N, Params>[keyof ImplementationTypes]}
|
|
||||||
|
|
||||||
// Now types used in the Dispatcher class itself
|
// Now types used in the Dispatcher class itself
|
||||||
|
|
||||||
@ -64,9 +134,9 @@ type SpecificationsGroup = Record<string, SpecObject>
|
|||||||
export class Dispatcher {
|
export class Dispatcher {
|
||||||
installSpecification(
|
installSpecification(
|
||||||
name: string,
|
name: string,
|
||||||
signature: Signature,
|
signature: InputSignature,
|
||||||
returns: TypeName,
|
returns: TypeName,
|
||||||
dependencies: Record<string, Signature>,
|
dependencies: Record<string, InputSignature>,
|
||||||
behavior: Function // possible todo: constrain this type based
|
behavior: Function // possible todo: constrain this type based
|
||||||
// on the signature, return type, and dependencies. Not sure if
|
// on the signature, return type, and dependencies. Not sure if
|
||||||
// that's really possible, though.
|
// that's really possible, though.
|
||||||
|
10
src/generic/all.ts
Normal file
10
src/generic/all.ts
Normal file
@ -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<Params>
|
||||||
|
extends ForType<'generic', GenericReturn<Params>> { }
|
||||||
|
}
|
39
src/generic/arithmetic.ts
Normal file
39
src/generic/arithmetic.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import {Dependency, ImpType, ImpReturns} from "../core/Dispatcher";
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface GenericReturn<Params> {
|
||||||
|
// Jos: not sure how to define this or why it is needed
|
||||||
|
// 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 =
|
||||||
|
<T>(dep: Dependency<'multiply', [T, T]>):
|
||||||
|
ImpType<'square', [T]> =>
|
||||||
|
z => dep.multiply(z, z)
|
3
src/generic/type.ts
Normal file
3
src/generic/type.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface GenericReturn<Params> {
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import {ForType} from '../core/Dispatcher.js'
|
import {ForType} from '../core/Dispatcher.js'
|
||||||
|
import {NumbersReturn} 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 ImplementationTypes extends ForType<'numbers', typeof numbers> {}
|
interface ReturnTypes<Params>
|
||||||
|
extends ForType<'numbers', NumbersReturn<Params>> {}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,70 @@
|
|||||||
import {configDependency} from '../core/Config.js'
|
import {configDependency} from '../core/Config.js'
|
||||||
import {Dependency} from '../core/Dispatcher.js'
|
import {
|
||||||
|
Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType
|
||||||
|
} from '../core/Dispatcher.js'
|
||||||
|
import type {Complex, UnderlyingReal} from '../Complex/type.js'
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface NumbersReturn<Params> {
|
||||||
|
// 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<Params, [number, number], number>
|
||||||
|
|
||||||
|
// 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<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
|
||||||
|
// 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<Params, [number], number | Complex<number>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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
|
|
||||||
export const sqrt =
|
export const sqrt =
|
||||||
(dep: configDependency
|
(dep: configDependency
|
||||||
& Dependency<'complex', [number, number]>) => {
|
& Dependency<'complex', [number, number]>): ImpType<'sqrt', [number]> => {
|
||||||
if (dep.config.predictable || !dep.complex) {
|
if (dep.config.predictable || !dep.complex) return conservativeSqrt
|
||||||
return (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
|
return a => {
|
||||||
}
|
|
||||||
return (a: number) => {
|
|
||||||
if (isNaN(a)) return NaN
|
if (isNaN(a)) return NaN
|
||||||
if (a >= 0) return Math.sqrt(a)
|
if (a >= 0) return Math.sqrt(a)
|
||||||
return dep.complex(0, Math.sqrt(unaryMinus(a)))
|
return dep.complex(0, Math.sqrt(unaryMinus(a)))
|
||||||
|
11
src/numbers/predicate.ts
Normal file
11
src/numbers/predicate.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {Signature, ImpType} from '../core/Dispatcher.js'
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface NumbersReturn<Params> {
|
||||||
|
isReal: Signature<Params, [number], true>
|
||||||
|
isSquare: Signature<Params, [number], boolean>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isReal: ImpType<'isReal', [number]> = a => true
|
||||||
|
export const isSquare: ImpType<'isSquare', [number]> = a => a >= 0
|
34
src/numbers/relational.ts
Normal file
34
src/numbers/relational.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {configDependency} from '../core/Config.js'
|
||||||
|
import {Signature, ImpType, Dependency} from '../core/Dispatcher.js'
|
||||||
|
|
||||||
|
const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16
|
||||||
|
|
||||||
|
declare module "./type" {
|
||||||
|
interface NumbersReturn<Params> {
|
||||||
|
equal: Signature<Params, [number, number], boolean>
|
||||||
|
unequal: Signature<Params, [number, number], boolean>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unequal = (dep: Dependency<'equal', [number, number]>):
|
||||||
|
ImpType<'unequal', [number, number]> =>
|
||||||
|
(x, y) => {
|
||||||
|
return !dep.equal(x, y)
|
||||||
|
}
|
@ -1,7 +1,44 @@
|
|||||||
|
import {ImpType} from '../core/Dispatcher.js'
|
||||||
|
import type {UnderlyingReal} from '../Complex/type.js'
|
||||||
|
|
||||||
export const number_type = {
|
export const number_type = {
|
||||||
before: ['Complex'],
|
before: ['Complex'],
|
||||||
test: (n: unknown): n is number => typeof n === 'number',
|
test: (n: unknown): n is number => typeof n === 'number',
|
||||||
from: {string: s => +s}
|
from: {string: s => +s}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const zero = (a: number) => 0
|
|
||||||
|
export interface NumbersReturn<Params> {
|
||||||
|
// 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<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 one: ImpType<'one', [number]> = a => 1
|
||||||
|
export const nan: ImpType<'nan', [number]> = a => NaN
|
||||||
|
export const re: ImpType<'re', [number]> = a => a
|
||||||
|
Loading…
Reference in New Issue
Block a user