feat: Narrow tsc typing of operation dependencies/implementations
This commit is contained in:
parent
90b66dc863
commit
f575582879
19 changed files with 912 additions and 111 deletions
2
src/Complex/all.ts
Normal file
2
src/Complex/all.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * as type_data from './type'
|
||||
export * as arithmetic_functions from './arithmetic'
|
152
src/Complex/arithmetic.ts
Normal file
152
src/Complex/arithmetic.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
import {Complex} from './type.js'
|
||||
|
||||
import {implementations, commonSpecs} from '@/core/Dispatcher'
|
||||
import type {RawDependencies} from '@/core/Dispatcher'
|
||||
import {commonSignature, RealType} from '@/interfaces/type'
|
||||
import type {CommonSignature, CommonReturn} from '@/interfaces/type'
|
||||
|
||||
// Narrowly typed signature selectors, for the operations we need to use
|
||||
// with atypical signatures:
|
||||
const add = 'add' as const
|
||||
const divide = 'divide' as const
|
||||
|
||||
export default function <T>() {
|
||||
const baseSignature = commonSpecs<T>()
|
||||
const withComplex
|
||||
= <RD extends RawDependencies>(rd: RD) => baseSignature({
|
||||
...rd, complex: {}
|
||||
})
|
||||
|
||||
const realSignature = commonSpecs<RealType<T>>()
|
||||
|
||||
return implementations<CommonSignature<Complex<T>>>()
|
||||
.dependent(withComplex({add: {}}), {
|
||||
add: dep => (w, z) =>
|
||||
dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im))
|
||||
})
|
||||
.dependent(withComplex({unaryMinus: {}}), {
|
||||
unaryMinus: dep => z =>
|
||||
dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
|
||||
})
|
||||
.dependent(withComplex({unaryMinus: {}, conj: {}}), {
|
||||
conj: dep => z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im))
|
||||
})
|
||||
.dependent(withComplex({subtract: {}}), {
|
||||
subtract: dep => (w, z) =>
|
||||
dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.re, z.re))
|
||||
})
|
||||
.dependent(withComplex({add: {}, subtract: {}, multiply: {}, conj: {}}), {
|
||||
multiply: dep => (w, z) => {
|
||||
const mult = dep.multiply
|
||||
const realpart = dep.subtract(
|
||||
mult( w.re, z.re), mult(dep.conj(w.im), z.im))
|
||||
const imagpart = dep.add(
|
||||
mult(dep.conj(w.re), z.im), mult( w.im, z.re))
|
||||
return dep.complex(realpart, imagpart)
|
||||
}
|
||||
})
|
||||
.dependent(baseSignature({
|
||||
absquare: {},
|
||||
add: {sig: commonSignature<'add', CommonReturn<'absquare', T>>()}
|
||||
}), {
|
||||
absquare: dep => z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
|
||||
})
|
||||
.dependent({
|
||||
conj: {},
|
||||
absquare: {},
|
||||
divideReal: {
|
||||
is: divide,
|
||||
sig: (a: Complex<T>, b: RealType<T>) => ({} as Complex<T>)
|
||||
}
|
||||
}, {
|
||||
reciprocal: dep => z => dep.divideReal(dep.conj(z), dep.absquare(z))
|
||||
})
|
||||
.dependent({multiply: {}, reciprocal: {}}, {
|
||||
divide: dep => (w,z) => dep.multiply(w, dep.reciprocal(z))
|
||||
})
|
||||
// The dependencies are tricky in the implementation of `sqrt` below,
|
||||
// because there are three types involved: Complex<T>, T, and
|
||||
// RealType<T>, all of which might be different.
|
||||
// We have to get it straight which operations we need on each type;
|
||||
// for example, we need `add` on three different combinations:
|
||||
.dependent({
|
||||
absquare: {}, re: {}, // Complex<T>-dependencies
|
||||
...withComplex({zero: {}}), // T-dependencies
|
||||
// And RealType<T>-dependencies:
|
||||
...realSignature({
|
||||
conservativeSqrt: {}, equal: {}, unaryMinus: {}, isnan: {}
|
||||
}),
|
||||
// And now mixed dependencies:
|
||||
divideReal: {
|
||||
is: divide,
|
||||
sig: (a: Complex<T>, b: RealType<T>) => ({} as Complex<T>)
|
||||
},
|
||||
addRR: {
|
||||
is: add,
|
||||
sig: (a: RealType<T>, b: RealType<T>) => ({} as RealType<T>)
|
||||
},
|
||||
addTR: {is: add, sig: (a: T, b: RealType<T>) => ({} as T)},
|
||||
addCR: {
|
||||
is: add,
|
||||
sig: (a: Complex<T>, b: RealType<T>) => ({} as Complex<T>)
|
||||
}
|
||||
}, {
|
||||
sqrt: dep => z => {
|
||||
const absq = dep.absquare(z)
|
||||
const myabs = dep.conservativeSqrt(absq)
|
||||
if (dep.isnan(myabs)) {
|
||||
throw new RangeError(
|
||||
`sqrt(${z}): cannot take square root of norm square ${absq}`
|
||||
)
|
||||
}
|
||||
const r = dep.re(z)
|
||||
const negr = dep.unaryMinus(r)
|
||||
if (dep.equal(myabs, negr)) {
|
||||
// pure imaginary square root; z.im already zero
|
||||
const rootNegr = dep.conservativeSqrt(negr)
|
||||
if (dep.isnan(rootNegr)) {
|
||||
throw new RangeError(
|
||||
`sqrt(${z}): cannot take square root of `
|
||||
+ `negative real part ${negr}`
|
||||
)
|
||||
}
|
||||
return dep.complex(
|
||||
dep.zero(z.re), dep.addTR(z.im, rootNegr))
|
||||
}
|
||||
const num = dep.addCR(z, myabs)
|
||||
const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r))
|
||||
const denom = dep.conservativeSqrt(denomsq)
|
||||
if (dep.isnan(denom)) {
|
||||
throw new RangeError(
|
||||
`sqrt(z) for z = ${z}: cannot take square root of `
|
||||
+ `2|z| + 2re(z) = ${denomsq}`
|
||||
)
|
||||
}
|
||||
return dep.divideReal(num, denom)
|
||||
}
|
||||
})
|
||||
.ship()
|
||||
}
|
||||
|
||||
// Additional implementations for non-uniform signatures
|
||||
export function mixed<T>() {
|
||||
return implementations<{
|
||||
add: (z: Complex<T>, r: RealType<T>) => Complex<T>,
|
||||
divide: (z: Complex<T>, r: RealType<T>) => Complex<T>,
|
||||
complex: CommonSignature<T>['complex']
|
||||
}>()
|
||||
.dependent({
|
||||
addTR: {is: add, sig: (a: T, b: RealType<T>) => ({} as T)},
|
||||
complex: {}
|
||||
}, {
|
||||
add: dep => (z, r) => dep.complex(dep.addTR(z.re, r), z.im)
|
||||
})
|
||||
.dependent({
|
||||
divTR: {is: divide, sig: (a: T, b: RealType<T>) => ({} as T)},
|
||||
complex: {}
|
||||
}, {
|
||||
divide: dep => (z, r) =>
|
||||
dep.complex(dep.divTR(z.re, r), dep.divTR(z.im, r))
|
||||
})
|
||||
.ship()
|
||||
}
|
71
src/Complex/type.ts
Normal file
71
src/Complex/type.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import {implementations, commonSpecs} from '@/core/Dispatcher'
|
||||
import {joinTypes} from '@/core/type'
|
||||
|
||||
import type {
|
||||
CommonInterface, CommonSignature, NaNType, OneType, ZeroType
|
||||
} from '@/interfaces/type'
|
||||
|
||||
export type Complex<T> = {re: T, im: T}
|
||||
|
||||
export const Complex_type = {
|
||||
name: 'Complex',
|
||||
test: <T>(dep: {testT: (z: unknown) => z is T}) =>
|
||||
(z: unknown): z is Complex<T> =>
|
||||
typeof z === 'object' && z != null && 're' in z && 'im' in z
|
||||
&& dep.testT(z.re) && dep.testT(z.im),
|
||||
infer: (dep: {typeOf: CommonSignature<undefined>['typeOf']}) =>
|
||||
(z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)),
|
||||
from: {
|
||||
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
|
||||
(z: Complex<U>) => ({re: dep.convert(z.re), im: dep.convert(z.im)}),
|
||||
T: <T>(dep: {zero: CommonSignature<T>['zero']}) =>
|
||||
(t: T) => ({re: t, im: dep.zero(t)})
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@/interfaces/type" {
|
||||
interface AssociatedTypes<T> {
|
||||
Complex: T extends Complex<infer R> ? {
|
||||
type: Complex<R>
|
||||
zero: Complex<ZeroType<R>>
|
||||
one: Complex<OneType<R> | ZeroType<R>>
|
||||
nan: Complex<NaNType<R>>
|
||||
real: RealType<R>
|
||||
closure: T
|
||||
} : never
|
||||
}
|
||||
|
||||
interface CommonInterface<T, Aux> {
|
||||
complex: (re: T, im?: T) => Complex<T>
|
||||
}
|
||||
}
|
||||
|
||||
// internal builder
|
||||
const cplex = <T>(a:T, b:T): Complex<T> => ({re: a, im: b})
|
||||
|
||||
export function lift<T>() {
|
||||
return implementations<CommonSignature<T>>()
|
||||
.dependent({zero: {}}, {
|
||||
complex: dep => (a, b) => cplex(a, b || dep.zero(a))
|
||||
}).ship()
|
||||
}
|
||||
|
||||
export default function <T>() {
|
||||
const baseSignature = commonSpecs<T>()
|
||||
|
||||
return implementations<CommonSignature<Complex<T>>>()
|
||||
.dependent(baseSignature({zero: {}}), {
|
||||
zero: dep => z => cplex(dep.zero(z.re), dep.zero(z.re))
|
||||
})
|
||||
.dependent(baseSignature({zero: {}, one: {}}), {
|
||||
one: dep => z =>
|
||||
cplex<OneType<T> | ZeroType<T>>(dep.one(z.re), dep.zero(z.re))
|
||||
})
|
||||
.dependent(baseSignature({nan: {}}), {
|
||||
nan: dep => z => cplex(dep.nan(z.re), dep.nan(z.re))
|
||||
})
|
||||
.dependent(baseSignature({re: {}}), {
|
||||
re: dep => z => dep.re(z.re)
|
||||
})
|
||||
.ship()
|
||||
}
|
2
src/all.ts
Normal file
2
src/all.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * as numbers from '@/numbers/all'
|
||||
export * as Complex from '@/Complex/all'
|
4
src/core/Configuration.ts
Normal file
4
src/core/Configuration.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type Configuration = {
|
||||
epsilon: number
|
||||
predictable: boolean
|
||||
}
|
201
src/core/Dispatcher.ts
Normal file
201
src/core/Dispatcher.ts
Normal file
|
@ -0,0 +1,201 @@
|
|||
import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type'
|
||||
|
||||
// A base type that roughly describes the dependencies of a single factory
|
||||
// for implementations of one operation. It is an object whose keys are the
|
||||
// identifiers are dependencies, and whose values describe that dependency.
|
||||
// In the value for a given key, the 'is' property gives the name of the
|
||||
// operation that dependency should be an instance of, defaulting to the key
|
||||
// itself when not present, and the 'sig' property gives the desired
|
||||
// signature for that operation. When the 'sig' property is not present,
|
||||
// the signature will default to some ambient ensemble of signatures.
|
||||
export type RawDependencies = Record<string, {is?: string, sig?: AnyFunc}>
|
||||
|
||||
// The following type transform fills in any unspecified signatures in RD
|
||||
// with the corresponding signatures from SomeSigs:
|
||||
type PatchedDepSpec<RD extends RawDependencies, SomeSigs extends GenSigs> = {
|
||||
[K in keyof RD]: RD[K] extends {sig: AnyFunc}
|
||||
? RD[K]
|
||||
: K extends keyof SomeSigs ? (RD[K] & {sig: SomeSigs[K]}) : RD[K]
|
||||
}
|
||||
|
||||
// A factory for building dependency specifications from the ensemble of
|
||||
// common signatures for a specific type (and perhaps auxiliary type). This
|
||||
// is typically used when describing implementation factories for one type
|
||||
// that depend on the common signatures for a *different* type.
|
||||
export function commonSpecs<
|
||||
T,
|
||||
Aux = T,
|
||||
CommonSigs extends GenSigs = CommonSignature<T, Aux>
|
||||
>() {
|
||||
return <RD extends RawDependencies>(
|
||||
rd: RD
|
||||
): PatchedDepSpec<RD, CommonSigs> => Object.fromEntries(
|
||||
Object.keys(rd).map(k => [
|
||||
k, 'sig' in rd[k] ? rd[k]
|
||||
: {...rd[k], sig: (() => undefined)}
|
||||
])
|
||||
) as PatchedDepSpec<RD, CommonSigs>
|
||||
}
|
||||
|
||||
// Further constraint on a dependency specification that means it is ready
|
||||
// to use with a given set of signatures:
|
||||
type DepSpec<Signatures extends GenSigs, Needs extends string>
|
||||
= {
|
||||
[K in Needs]: K extends keyof Signatures
|
||||
? {sig?: AnyFunc}
|
||||
: {is: keyof Signatures, sig: AnyFunc}
|
||||
}
|
||||
|
||||
// Just checks if an RawDependencies is really a DepSpec, and blanks it out if not
|
||||
type DepCheck<
|
||||
RD extends RawDependencies,
|
||||
Signatures extends GenSigs,
|
||||
Needs extends string = keyof RD & string
|
||||
> = RD extends DepSpec<Signatures, Needs> ? RD
|
||||
: {
|
||||
[K in Needs]: K extends keyof Signatures ? {}
|
||||
: {is: never, sig: (q: boolean) => void}
|
||||
}
|
||||
|
||||
// The actual type of a dependency, given a dependency specification
|
||||
type DepType<
|
||||
Signatures extends GenSigs,
|
||||
DS extends DepSpec<Signatures, (keyof DS & string)>
|
||||
> = {[K in keyof DS]: DS[K] extends {sig: AnyFunc}
|
||||
? DS[K]['sig']
|
||||
: K extends keyof Signatures ? Signatures[K] : never
|
||||
}
|
||||
|
||||
// A collection of dependency specifications for some of the operations in
|
||||
// an ensemble of Signatures:
|
||||
type Specifications<
|
||||
Signatures extends GenSigs,
|
||||
NeedKeys extends keyof Signatures & string,
|
||||
NeedList extends Record<NeedKeys, string>
|
||||
> = {[K in NeedKeys]: DepSpec<Signatures, NeedList[K]>}
|
||||
|
||||
// The type of a factory function for implementations of a dependent operation,
|
||||
// given a dependency specification:
|
||||
type FactoryType<
|
||||
Signatures extends GenSigs,
|
||||
K extends (keyof Signatures) & string,
|
||||
DS extends DepSpec<Signatures, (keyof DS & string)>
|
||||
> = (dep: DepType<Signatures, DS>) => Signatures[K]
|
||||
|
||||
// The type of an implementation specification for an operation given its
|
||||
// dependency specification: either directly the implementation if there
|
||||
// are actually no dependencies, or a factory function and collection of
|
||||
// dependency names otherwise:
|
||||
type ImpType<
|
||||
Signatures extends GenSigs,
|
||||
K extends (keyof Signatures) & string,
|
||||
DS extends DepSpec<Signatures, (keyof DS & string)>
|
||||
> = DS extends null ? {implementation: Signatures[K]}
|
||||
: {factory: FactoryType<Signatures, K, DS>, dependencies: DS}
|
||||
|
||||
// A collection of implementations for some operations of an ensemble of
|
||||
// Signatures, matching a given collection of dependency specifications
|
||||
type Implementations<
|
||||
Signatures extends GenSigs,
|
||||
NeedKeys extends keyof Signatures & string,
|
||||
NeedList extends Record<NeedKeys, string>,
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
|
||||
|
||||
// The builder interface that lets us assemble narrowly-typed Implementations:
|
||||
interface ImplementationBuilder<
|
||||
Signatures extends GenSigs,
|
||||
NeedKeys extends keyof Signatures & string,
|
||||
NeedList extends Record<NeedKeys, string>,
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
> {
|
||||
independent<NewKeys extends (keyof Signatures) & string>(
|
||||
independentImps: {[K in NewKeys]: Signatures[K]}
|
||||
): ImplementationBuilder<
|
||||
Signatures,
|
||||
NeedKeys | NewKeys,
|
||||
NeedList & {[K in NewKeys]: never},
|
||||
Specs & {[K in NewKeys]: null}
|
||||
>
|
||||
|
||||
dependent<
|
||||
RD extends RawDependencies, // Easier to infer
|
||||
NewKeys extends (keyof Signatures) & string,
|
||||
DepKeys extends string = keyof RD & string
|
||||
>(
|
||||
depSpec: RD,
|
||||
imps: {
|
||||
[K in NewKeys]:
|
||||
FactoryType<Signatures, K, DepCheck<RD, Signatures>>
|
||||
}
|
||||
): ImplementationBuilder<
|
||||
Signatures,
|
||||
NeedKeys | NewKeys,
|
||||
NeedList & {[K in NewKeys]: DepKeys},
|
||||
Specs & {[K in NewKeys]: DepCheck<RD, Signatures>}
|
||||
>
|
||||
|
||||
ship(): Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||
}
|
||||
|
||||
// And a function that actually provides the builder interface:
|
||||
function impBuilder<
|
||||
Signatures extends GenSigs,
|
||||
NeedKeys extends keyof Signatures & string,
|
||||
NeedList extends Record<NeedKeys, string>,
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
>(
|
||||
sofar: Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||
): ImplementationBuilder<Signatures, NeedKeys, NeedList, Specs> {
|
||||
return {
|
||||
independent<NewKeys extends (keyof Signatures) & string>(
|
||||
imps: {[K in NewKeys]: Signatures[K]}) {
|
||||
return impBuilder({
|
||||
...sofar,
|
||||
...Object.fromEntries(Object.keys(imps).map(k => [k, {
|
||||
implementation: imps[k]
|
||||
}]))
|
||||
} as Implementations<
|
||||
Signatures,
|
||||
NeedKeys | NewKeys,
|
||||
NeedList & {[K in NewKeys]: never},
|
||||
Specs & {[K in NewKeys]: null}
|
||||
>)
|
||||
},
|
||||
|
||||
dependent<
|
||||
RD extends RawDependencies,
|
||||
NewKeys extends (keyof Signatures) & string,
|
||||
DepKeys extends string = keyof RD & string
|
||||
>(
|
||||
depSpec: RD,
|
||||
imps: {
|
||||
[K in NewKeys]:
|
||||
FactoryType<Signatures, K, DepCheck<RD, Signatures, DepKeys>>
|
||||
}
|
||||
) {
|
||||
return impBuilder({
|
||||
...sofar,
|
||||
...Object.fromEntries(Object.keys(imps).map(k => [k, {
|
||||
factory: imps[k],
|
||||
dependencies: depSpec
|
||||
}]))
|
||||
}) as unknown as ImplementationBuilder<
|
||||
Signatures,
|
||||
NeedKeys | NewKeys,
|
||||
NeedList & {[K in NewKeys]: DepKeys},
|
||||
Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>}
|
||||
>
|
||||
},
|
||||
ship() {
|
||||
return (sofar as
|
||||
Implementations<Signatures, NeedKeys, NeedList, Specs>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A convenience function that gives you an implementation builder:
|
||||
export function implementations<Signatures extends GenSigs>(
|
||||
): ImplementationBuilder<Signatures, never, {}, {}> {
|
||||
return impBuilder({})
|
||||
}
|
7
src/core/type.ts
Normal file
7
src/core/type.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import type {TypeName} from '@/interfaces/type'
|
||||
|
||||
// Dummy implementation for now
|
||||
export function joinTypes(a: TypeName, b: TypeName) {
|
||||
if (a === b) return a
|
||||
return 'any'
|
||||
}
|
5
src/index.ts
Normal file
5
src/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import {inspect} from 'node:util'
|
||||
|
||||
import * as specifications from './all'
|
||||
|
||||
console.log(inspect(specifications, {depth: 8, colors: true}))
|
20
src/interfaces/arithmetic.ts
Normal file
20
src/interfaces/arithmetic.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import type {ClosureType, NaNType, RealType} from './type'
|
||||
|
||||
type UnOp<T> = (a: T) => T
|
||||
type BinOp<T> = (a: T, B: T) => T
|
||||
|
||||
declare module "./type" {
|
||||
interface CommonInterface<T, Aux> {
|
||||
add: BinOp<T>
|
||||
unaryMinus: UnOp<T>
|
||||
conj: UnOp<T>
|
||||
subtract: BinOp<T>
|
||||
multiply: BinOp<T>
|
||||
square: UnOp<T>
|
||||
absquare: (a: T) => RealType<T>
|
||||
reciprocal: UnOp<T>
|
||||
divide: BinOp<T>
|
||||
conservativeSqrt: (a: T) => (T | NaNType<T>)
|
||||
sqrt: (a: T) => (T | ClosureType<T>)
|
||||
}
|
||||
}
|
11
src/interfaces/relational.ts
Normal file
11
src/interfaces/relational.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type {NaNType} from './type.ts'
|
||||
|
||||
export type BinaryPredicate<T> = (a: T, b: T) => boolean
|
||||
|
||||
declare module "./type" {
|
||||
interface CommonInterface<T, Aux> {
|
||||
equal: BinaryPredicate<T>
|
||||
unequal: BinaryPredicate<T>
|
||||
isnan: (a: T | NaNType<T>) => a is NaNType<T>
|
||||
}
|
||||
}
|
116
src/interfaces/type.ts
Normal file
116
src/interfaces/type.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import {Configuration} from '@/core/Configuration'
|
||||
|
||||
/* First some type utilities: */
|
||||
|
||||
export type ToMapped<T> = {[K in keyof T]: T[K]}
|
||||
|
||||
export type UnionToIntersection<U> =
|
||||
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
|
||||
? I : never
|
||||
|
||||
export type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
|
||||
[P in TKey]: (k: T[P]) => void
|
||||
} [TKey] extends ((k: infer I) => void) ? I : never
|
||||
|
||||
|
||||
// The following needs to be a type that is extended by
|
||||
// the type of any function. Hopefully I have gotten this right.
|
||||
export type AnyFunc = (...args: never[]) => unknown
|
||||
|
||||
// An ensemble of signatures of operations, for example, the CommonSignatures
|
||||
// collected up by all of the modules
|
||||
export type GenSigs = Record<string, AnyFunc>
|
||||
|
||||
/*****
|
||||
* Every core math type has some associated types; they need
|
||||
* to be published in the following interface. The key is the
|
||||
* name of the type, and within the subinterface for that key,
|
||||
* the type of the 'type' property is the actual TypeScript type
|
||||
* we are associating the other properties to. To get an associated type,
|
||||
* types are looked up by matching this 'type' property.
|
||||
*
|
||||
* Note the interface is generic with one parameter. To actually find
|
||||
* the associated types of type T, instantiate the interface with type
|
||||
* T. This mechanism deals with the fact that TypeScript doesn't really
|
||||
* deal well with interfaces, some entries of which are generic and others
|
||||
* are not.
|
||||
****/
|
||||
|
||||
export interface AssociatedTypes<T> {
|
||||
undefined: {
|
||||
type: undefined
|
||||
zero: undefined // The type of the zero of this type
|
||||
one: undefined // The type of the multiplicative identity of this type
|
||||
nan: undefined // The type of Not a Number of this type
|
||||
real: undefined // The type of the real part of this type
|
||||
closure: undefined // The type of the algebraic closure of this type
|
||||
}
|
||||
}
|
||||
|
||||
export type TypeName = string // Really should be some recursive definition,
|
||||
// any key of AssociatedTypes that's not generic, or any key that is generic
|
||||
// but instantatied by a TypeName. Not sure how to do that, so just go with
|
||||
// any string for now.
|
||||
|
||||
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
|
||||
type ALookup<T, Name extends AssociatedTypeNames> =
|
||||
ValueIntersectionByKeyUnion<
|
||||
{[K in keyof AssociatedTypes<T>]:
|
||||
T extends AssociatedTypes<T>[K]['type']
|
||||
? AssociatedTypes<T>[K][Name] : unknown
|
||||
},
|
||||
keyof AssociatedTypes<T>
|
||||
>
|
||||
|
||||
// For everything to compile, zero and one must be subtypes of T:
|
||||
export type ZeroType<T> = ALookup<T, 'zero'> & T
|
||||
export type OneType<T> = ALookup<T, 'one'> & T
|
||||
// But I believe 'nan' really might not be, like I think we will have to use
|
||||
// number NaN for the nan of 'bigint', as it has nothing at all like NaN,
|
||||
// so don't force it:
|
||||
export type NaNType<T> = ALookup<T, 'nan'>
|
||||
export type RealType<T> = ALookup<T, 'real'>
|
||||
export type ClosureType<T> = ALookup<T, 'closure'>
|
||||
|
||||
/*****
|
||||
* The typical signature for every operation needs to be published in the
|
||||
* following interface. Each key is the name of an operation.
|
||||
* The type of each key should be the function type that the operation would
|
||||
* "normally" have, understanding that there may be exceptions, which will\
|
||||
* be dealt with by another mechanism.
|
||||
*
|
||||
* Note that this interface is generic in two parameters, the second of which
|
||||
* defaults to the first. These are slots for type parameters to the typical
|
||||
* signatures, most of which have only one type parameter.
|
||||
****/
|
||||
|
||||
export interface CommonInterface<T, Aux = T> {
|
||||
zero: (a: T) => ZeroType<T>
|
||||
one: (a: T) => OneType<T>
|
||||
nan: (a: T | NaNType<T>) => NaNType<T>
|
||||
re: (a: T) => RealType<T>
|
||||
|
||||
config: () => Configuration
|
||||
convert: (from: T) => Aux
|
||||
typeOf: (x: unknown) => TypeName
|
||||
}
|
||||
|
||||
export type CommonSignature<T, Aux = T> = ToMapped<CommonInterface<T, Aux>>
|
||||
export type SignatureKey<T, Aux = T> = keyof CommonSignature<T, Aux>
|
||||
|
||||
export function commonSignature<
|
||||
K, T, Aux = T, CS extends GenSigs = CommonSignature<T, Aux>
|
||||
>(): K extends keyof CS ? CS[K] : () => void {
|
||||
return (() => undefined) as K extends keyof CS ? CS[K] : () => void
|
||||
}
|
||||
|
||||
export type CommonReturn<
|
||||
K, T, Aux = T, CS extends GenSigs = CommonSignature<T, Aux>
|
||||
> = K extends keyof CS ? ReturnType<CS[K]> : void
|
||||
|
||||
export type Dependency<T, Aux = T> = {
|
||||
[K in SignatureKey<T, Aux>]: Pick<CommonSignature<T, Aux>, K>
|
||||
}
|
||||
|
||||
export type Dependencies<T, Names extends SignatureKey<T>, Aux = T> =
|
||||
Pick<CommonSignature<T, Aux>, Names>
|
2
src/numbers/all.ts
Normal file
2
src/numbers/all.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * as type_data from './type'
|
||||
export * as arithmetic_functions from './arithmetic'
|
26
src/numbers/arithmetic.ts
Normal file
26
src/numbers/arithmetic.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {implementations} from '@/core/Dispatcher'
|
||||
import type {CommonSignature} from '@/interfaces/type'
|
||||
|
||||
const conservativeSqrt = (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
|
||||
|
||||
export default implementations<CommonSignature<number>>()
|
||||
.independent({
|
||||
add: (a, b) => a + b,
|
||||
unaryMinus: a => -a,
|
||||
subtract: (a, b) => a - b,
|
||||
multiply: (a, b) => a * b,
|
||||
absquare: a => a * a,
|
||||
reciprocal: a => 1 / a,
|
||||
divide: (a, b) => a / b,
|
||||
conj: a => a,
|
||||
conservativeSqrt })
|
||||
.dependent({config: {}, complex: {}}, {
|
||||
sqrt: dep => {
|
||||
if (dep.config().predictable || !dep.complex) return conservativeSqrt
|
||||
return a => {
|
||||
if (isNaN(a)) return NaN
|
||||
if (a >= 0) return Math.sqrt(a)
|
||||
return dep.complex(0, Math.sqrt(-a))
|
||||
}
|
||||
}})
|
||||
.ship()
|
31
src/numbers/type.ts
Normal file
31
src/numbers/type.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import type {Complex} from '@/Complex/type'
|
||||
import {implementations} from '@/core/Dispatcher'
|
||||
import type {CommonSignature} from '@/interfaces/type'
|
||||
|
||||
export const number_type = {
|
||||
name: 'number',
|
||||
before: ['Complex'],
|
||||
test: (n: unknown): n is number => typeof n === 'number',
|
||||
from: {string: (s: string) => +s }
|
||||
}
|
||||
|
||||
declare module "@/interfaces/type" {
|
||||
interface AssociatedTypes<T> {
|
||||
number: {
|
||||
type: number
|
||||
zero: 0
|
||||
one: 1
|
||||
nan: typeof NaN
|
||||
real: number
|
||||
closure: Complex<number>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default implementations<CommonSignature<number>>()
|
||||
.independent({
|
||||
zero: a => 0,
|
||||
one: a => 1,
|
||||
nan: a => NaN,
|
||||
re: a => a
|
||||
}).ship()
|
Loading…
Add table
Add a link
Reference in a new issue