refactor: tighter universal interface types

This commit is contained in:
Glen Whitney 2022-12-24 00:41:35 -05:00
parent a5848125e4
commit 74e2aef524
16 changed files with 251 additions and 214 deletions

View File

@ -1,103 +1,96 @@
import {Complex, complex_binary, FnComplexUnary} from './type.js'
import {Complex, ComplexOp} from './type.js'
import type {
FnAbsSquare,
FnAdd,
FnAddReal,
FnConj, FnConservativeSqrt, FnDivide,
FnDivideByReal, FnIsReal, FnIsSquare,
FnMultiply, FnNaN, FnRe, FnReciprocal, FnSqrt,
FnSubtract,
FnUnaryMinus, FnZero
} from '../interfaces/arithmetic'
AbsquareOp, AddOp, AddRealOp, ConjOp, ConservativeSqrtOp, DivideOp,
DivideByRealOp, MultiplyOp, ReciprocalOp, SqrtOp, SubtractOp,
UnaryMinusOp
} from '../interfaces/arithmetic.js'
import type {
NanOp, ReOp, ZeroOp, Depends, RealType, WithConstants, NaNType
} from '../interfaces/type.js'
import type {IsSquareOp, IsRealOp} from '../interfaces/predicate.js'
export const add =
<T>(dep: {
add: FnAdd<T>
}): FnAdd<Complex<T>> =>
(w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im))
<T>(dep: Depends<AddOp<T>> & Depends<ComplexOp<T>>): AddOp<Complex<T>> =>
(w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im))
export const addReal =
<T>(dep: {
addReal: FnAddReal<T, T>
}): FnAddReal<Complex<T>, T> =>
(z, r) => complex_binary(dep.addReal(z.re, r), z.im)
<T>(dep: Depends<AddRealOp<T>> & Depends<ComplexOp<T>>):
AddRealOp<Complex<T>> =>
(z, r) => dep.complex(dep.addReal(z.re, r), z.im)
export const unaryMinus =
<T>(dep: {
unaryMinus: FnUnaryMinus<T>
}): FnUnaryMinus<Complex<T>> =>
(z) => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
<T>(dep: Depends<UnaryMinusOp<T>> & Depends<ComplexOp<T>>):
UnaryMinusOp<Complex<T>> =>
z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
export const conj =
<T>(dep: {
unaryMinus: FnUnaryMinus<T>,
conj: FnConj<T>
}) : FnConj<Complex<T>> =>
(z) => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im))
<T>(dep: Depends<UnaryMinusOp<T>>
& Depends<ConjOp<T>>
& Depends<ComplexOp<T>>):
ConjOp<Complex<T>> =>
z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im))
export const subtract =
<T>(dep: {
subtract: FnSubtract<T>
}): FnSubtract<Complex<T>> =>
(w, z) => complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im))
<T>(dep: Depends<SubtractOp<T>> & Depends<ComplexOp<T>>):
SubtractOp<Complex<T>> =>
(w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im))
export const multiply =
<T>(dep: {
add: FnAdd<T>,
subtract: FnSubtract<T>,
multiply: FnMultiply<T>,
conj: FnConj<T>
}) =>
<T>(dep: Depends<AddOp<T>>
& Depends<SubtractOp<T>>
& Depends<MultiplyOp<T>>
& Depends<ConjOp<T>>
& Depends<ComplexOp<T>>):
MultiplyOp<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)
const realpart = dep.subtract(
mult( w.re, z.re), mult(dep.conj(w.im), z.im))
const imagpart = dep.add(
mult(dep.conj(w.re), z.im), mult( w.im, z.re))
return dep.complex(realpart, imagpart)
}
export const absquare =
<T, U>(dep: {
add: FnAdd<U>,
absquare: FnAbsSquare<T, U>
}): FnAbsSquare<Complex<T>, U> =>
(z) => dep.add(dep.absquare(z.re), dep.absquare(z.im))
<T>(dep: Depends<AddOp<RealType<T>>> & Depends<AbsquareOp<T>>):
AbsquareOp<Complex<T>> =>
z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
export const divideByReal =
<T>(dep: {
divideByReal: FnDivideByReal<T, T>
}): FnDivideByReal<Complex<T>, T> =>
(z, r) => complex_binary(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r))
<T>(dep: Depends<DivideByRealOp<T>> & Depends<ComplexOp<T>>):
DivideByRealOp<Complex<T>> =>
(z, r) => dep.complex(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r))
export const reciprocal =
<T>(dep: {
conj: FnConj<Complex<T>>,
absquare: FnAbsSquare<Complex<T>, T>,
divideByReal: FnDivideByReal<Complex<T>, T>
}): FnReciprocal<Complex<T>> =>
(z) => dep.divideByReal(dep.conj(z), dep.absquare(z))
<T>(dep: Depends<ConjOp<Complex<T>>>
& Depends<AbsquareOp<Complex<T>>>
& Depends<DivideByRealOp<Complex<T>>>):
ReciprocalOp<Complex<T>> =>
z => dep.divideByReal(dep.conj(z), dep.absquare(z))
export const divide =
<T>(dep: {
multiply: FnMultiply<Complex<T>>,
reciprocal: FnReciprocal<Complex<T>>,
}): FnDivide<Complex<T>> =>
<T>(dep: Depends<MultiplyOp<Complex<T>>>
& Depends<ReciprocalOp<Complex<T>>>):
DivideOp<Complex<T>> =>
(w, z) => dep.multiply(w, dep.reciprocal(z))
export type ComplexSqrtOp<T> = {
op?: 'complexSqrt',
(a: T): Complex<WithConstants<T> | NaNType<WithConstants<T>>>
}
// Complex square root of a real type T
export const complexSqrt =
<T>(dep: {
conservativeSqrt: FnConservativeSqrt<T>,
isSquare: FnIsSquare<T>,
complex: FnComplexUnary<T>,
unaryMinus: FnUnaryMinus<T>,
zero: FnZero<T>,
nan: FnNaN<Complex<T>>
}) =>
(r) => {
<T>(dep: Depends<ConservativeSqrtOp<T>>
& Depends<IsSquareOp<T>>
& Depends<UnaryMinusOp<T>>
& Depends<ComplexOp<WithConstants<T>>>
& Depends<ZeroOp<T>>
& Depends<NanOp<Complex<WithConstants<T>>>>): ComplexSqrtOp<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))
return dep.complex(dep.zero(r), dep.conservativeSqrt(negative))
}
// neither the real number or its negative is a square; could happen
// for example with bigint. So there is no square root. So we have to
@ -106,17 +99,15 @@ export const complexSqrt =
}
export const sqrt =
<T>(dep: {
isReal: FnIsReal<Complex<T>>,
complexSqrt: FnSqrt<T>,
conservativeSqrt: FnConservativeSqrt<T>,
absquare: FnAbsSquare<Complex<T>, T>,
addReal: FnAddReal<Complex<T>, T>,
divideByReal: FnDivideByReal<Complex<T>, T>,
add: FnAdd<T>,
re: FnRe<Complex<T>, T>,
}) =>
(z: Complex<T>): Complex<T> | T => {
<T>(dep: Depends<IsRealOp<Complex<T>>>
& Depends<ComplexSqrtOp<T>>
& Depends<ConservativeSqrtOp<RealType<T>>>
& Depends<AbsquareOp<Complex<T>>>
& Depends<AddRealOp<Complex<T>>>
& Depends<DivideByRealOp<Complex<T>>>
& Depends<AddOp<RealType<T>>>
& Depends<ReOp<Complex<T>>>): SqrtOp<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)

View File

@ -1,14 +1,12 @@
import {Complex} from './type.js'
import {FnEqual} from '../interfaces/relational'
import {FnAdd, FnIsReal, FnIsSquare} from '../interfaces/arithmetic'
import {EqualOp} from '../interfaces/relational.js'
import {AddOp} from '../interfaces/arithmetic.js'
import type {Depends} from '../interfaces/type.js'
import type {IsRealOp, IsSquareOp} from '../interfaces/predicate.js'
export const isReal =
<T>(dep: {
equal: FnEqual<T>,
add: FnAdd<T>,
isReal: FnIsReal<T>
}): FnIsReal<Complex<T>> =>
(z) => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
<T>(dep: Depends<AddOp<T>> & Depends<EqualOp<T>> & Depends<IsRealOp<T>>):
IsRealOp<Complex<T>> =>
z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
export const isSquare =
<T>(): FnIsSquare<Complex<T>> => (z) => true // FIXME: not correct for Complex<bigint> once we get there
export const isSquare: IsSquareOp<Complex<any>> = z => true // FIXME: not correct for Complex<bigint> once we get there

View File

@ -1,8 +1,7 @@
import {Complex} from './type.js'
import {FnEqual} from '../interfaces/relational'
import {Depends} from '../interfaces/type.js'
import {EqualOp} from '../interfaces/relational.js'
export const equal =
<T>(dep: {
equal: FnEqual<T>
}): FnEqual<Complex<T>> =>
<T>(dep: Depends<EqualOp<T>>): EqualOp<Complex<T>> =>
(w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im)

View File

@ -1,7 +1,9 @@
import {
joinTypes, typeOfDependency, Dependency,
} from '../core/Dispatcher.js'
import type {FnNaN, FnOne, FnRe, FnZero} from '../interfaces/arithmetic.js'
import type {
OneOp, ZeroOp, NanOp, ReOp, ZeroType, OneType, NaNType, Depends
} from '../interfaces/type.js'
export type Complex<T> = { re: T; im: T; }
@ -20,39 +22,41 @@ export const Complex_type = {
}
}
export type FnComplexUnary<T> = (t: T) => Complex<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>
} : never
}
}
export const complex_unary =
<T>(dep: {
zero: FnZero<T>
}): FnComplexUnary<T> =>
(t) => ({ re: t, im: dep.zero(t) })
export type ComplexOp<T> = {op?: 'complex', (a: T, b?: T): Complex<T>}
export type FnComplexBinary<T> = (re: T, im: T) => Complex<T>
export const complex_binary = <T>(t: T, u: T): Complex<T> => ({ re: t, im: u })
export const complex =
<T>(dep: Depends<ZeroOp<T>>): ComplexOp<T | ZeroType<T>> =>
(a, b) => ({re: a, im: b || dep.zero(a)})
export const zero =
<T>(dep: {
zero: FnZero<T>
}): FnZero<Complex<T>> =>
(z) => complex_binary(dep.zero(z.re), dep.zero(z.im))
<T>(dep: Depends<ZeroOp<T>> & Depends<ComplexOp<ZeroType<T>>>):
ZeroOp<Complex<T>> =>
z => dep.complex(dep.zero(z.re), dep.zero(z.im))
export const one =
<T>(dep: {
zero: FnZero<T>,
one: FnOne<T>
}): FnOne<Complex<T>> =>
(z) => complex_binary(dep.one(z.re), dep.zero(z.im))
<T>(dep: Depends<OneOp<T>>
& Depends<ZeroOp<T>>
& Depends<ComplexOp<ZeroType<T>|OneType<T>>>):
OneOp<Complex<T>> =>
z => dep.complex(dep.one(z.re), dep.zero(z.im))
export const nan =
<T>(dep: {
nan: FnNaN<T>
}): FnNaN<Complex<T>> =>
(z) => complex_binary(dep.nan(z.re), dep.nan(z.im))
<T>(dep: Depends<NanOp<T>> & Depends<ComplexOp<NaNType<T>>>):
NanOp<Complex<T>> =>
z => dep.complex(dep.nan(z.re), dep.nan(z.im))
export const re =
<T>(dep: {
re: FnRe<T,T>
}): FnRe<Complex<T>, T> =>
(z) => dep.re(z.re)
<T>(dep: Depends<ReOp<T>>): ReOp<Complex<T>> =>
z => dep.re(z.re)

View File

@ -1,3 +1 @@
import * as generic from './arithmetic.js'
export { generic }
export * as generic from './native.js'

View File

@ -1,7 +1,6 @@
import type { FnMultiply, FnSquare } from "../interfaces/arithmetic"
import type {Depends} from '../interfaces/type.js'
import type {MultiplyOp, SquareOp} from '../interfaces/arithmetic.js'
export const square =
<T>(dep: {
multiply: FnMultiply<T>
}): FnSquare<T> =>
(z) => dep.multiply(z, z)
<T>(dep: Depends<MultiplyOp<T>>): SquareOp<T> =>
z => dep.multiply(z, z)

2
src/generic/native.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './arithmetic.js'
export * from './relational.js'

View File

@ -0,0 +1,5 @@
import {Depends} from '../interfaces/type.js'
import type {EqualOp, UnequalOp} from '../interfaces/relational.js'
export const unequal = <T>(dep: Depends<EqualOp<T>>): UnequalOp<T> =>
(x, y) => !dep.equal(x, y)

View File

@ -1,28 +1,25 @@
// shared interfaces
import type {Complex} from '../Complex/type.js'
import type {RealType, WithConstants, NaNType} from './type.js'
import { Complex } from "../Complex/type"
// Note: right now I've added an 'Fn*' prefix,
// so it is clear that the type hold a function type definition
// Note: right now I've added an 'Op' suddix,
// so it is clear that the type holds the function type of an operation
// This is not necessary though, it is just a naming convention.
export type FnAdd<T> = (a: T, b: T) => T
export type FnAddReal<T, U> = (a: T, b: U) => T
export type FnUnaryMinus<T> = (a: T) => T
export type FnConj<T> = (a: T) => T
export type FnSubtract<T> = (a: T, b: T) => T
export type FnMultiply<T> = (a: T, b: T) => T
export type FnAbsSquare<T, U> = (a: T) => U
export type FnReciprocal<T> = (a: T) => T
export type FnDivide<T> = (a: T, b: T) => T
export type FnDivideByReal<T, U> = (a: T, b: U) => T
export type FnConservativeSqrt<T> = (a: T) => T
export type FnSqrt<T> = (a: T) => T | Complex<T>
export type FnSquare<T> = (z: T) => T
export type AddOp<T> = {op?: 'add', (a: T, b: T): T}
export type AddRealOp<T> = {op?: 'addReal', (a: T, b: RealType<T>): T}
export type UnaryMinusOp<T> = {op?: 'unaryMinus', (a: T): T}
export type ConjOp<T> = {op?: 'conj', (a: T): T}
export type SubtractOp<T> = {op?: 'subtract', (a: T, b: T): T}
export type MultiplyOp<T> = {op?: 'multiply', (a: T, b: T): T}
export type AbsquareOp<T> = {op?: 'absquare', (a: T): RealType<T>}
export type ReciprocalOp<T> = {op?: 'reciprocal', (a: T): T}
export type DivideOp<T> = {op?: 'divide', (a: T, b: T): T}
export type DivideByRealOp<T> = {op?: 'divideByReal', (a: T, b: RealType<T>): T}
export type ConservativeSqrtOp<T> = {op?: 'conservativeSqrt', (a: T): T}
export type SqrtOp<T> = {
op?: 'sqrt',
(a: T): T extends Complex<infer R>
? Complex<WithConstants<R> | NaNType<WithConstants<R>>>
: T | Complex<T>
}
export type SquareOp<T> = {op?: 'square', (z: T): T}
export type FnIsReal<T> = (a: T) => boolean
export type FnIsSquare<T> = (a: T) => boolean
export type FnZero<T> = (a: T) => T
export type FnOne<T> = (a: T) => T
export type FnNaN<T> = (a: T) => T
export type FnRe<T, U> = (a: T) => U

View File

@ -0,0 +1,2 @@
export type IsRealOp<T> = {op?: 'isReal', (a: T): boolean}
export type IsSquareOp<T> = {op?: 'isSquare', (a: T): boolean}

View File

@ -1,3 +1,2 @@
export type FnEqual<T> = (a: T, b: T) => boolean
export type FnUnequal<T> = (a: T, b: T) => boolean
export type EqualOp<T> = {op?: 'equal', (a: T, b: T): boolean}
export type UnequalOp<T> = {op?: 'unequal', (a: T, b: T): boolean}

32
src/interfaces/type.ts Normal file
View File

@ -0,0 +1,32 @@
export interface AssociatedTypes<T> {
undefined: {
type: undefined
zero: undefined
one: undefined
nan: undefined
real: undefined
}
}
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
export type Lookup<T, Name extends AssociatedTypeNames> = {
[K in keyof AssociatedTypes<T>]:
T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : never
}[keyof AssociatedTypes<T>]
export type ZeroType<T> = Lookup<T, 'zero'>
export type OneType<T> = Lookup<T, 'one'>
export type WithConstants<T> = T | ZeroType<T> | OneType<T>
export type NaNType<T> = Lookup<T, 'nan'>
export type RealType<T> = Lookup<T, 'real'>
export type ZeroOp<T> = {op?: 'zero', (a: WithConstants<T>): ZeroType<T>}
export type OneOp<T> = {op?: 'one', (a: WithConstants<T>): OneType<T>}
export type NanOp<T> = {op?: 'nan', (a: T|NaNType<T>): NaNType<T>}
export type ReOp<T> = {op?: 're', (a: T): RealType<T>}
type NamedFunction = {op?: string, (...params: any[]): any}
export type Depends<FuncType extends NamedFunction> =
{[K in FuncType['op']]: FuncType}

View File

@ -1,26 +1,28 @@
import { Config } from '../core/Config.js'
import type { FnComplexBinary } from '../Complex/type.js'
import { FnAdd, FnConj, FnSubtract, FnUnaryMinus, FnMultiply, FnAbsSquare, FnReciprocal, FnDivide, FnConservativeSqrt, FnSqrt } from '../interfaces/arithmetic.js'
import type {configDependency} from '../core/Config.js'
import type {ComplexOp} from '../Complex/type.js'
import type {
AddOp, ConjOp, SubtractOp, UnaryMinusOp, MultiplyOp,
AbsquareOp, ReciprocalOp, DivideOp, ConservativeSqrtOp, SqrtOp
} from '../interfaces/arithmetic.js'
import type {Depends} from '../interfaces/type.js'
export const add: FnAdd<number> = (a, b) => a + b
export const add: AddOp<number> = (a, b) => a + b
export const addReal = add
export const unaryMinus: FnUnaryMinus<number> = (a) => -a
export const conj: FnConj<number> = (a) => a
export const subtract: FnSubtract<number> = (a, b) => a - b
export const multiply: FnMultiply<number> = (a, b) => a * b
export const absquare: FnAbsSquare<number, number> = (a) => a * a
export const reciprocal: FnReciprocal<number> = (a) => 1 / a
export const divide: FnDivide<number> = (a, b) => a / b
export const unaryMinus: UnaryMinusOp<number> = a => -a
export const conj: ConjOp<number> = a => a
export const subtract: SubtractOp<number> = (a, b) => a - b
export const multiply: MultiplyOp<number> = (a, b) => a * b
export const absquare: AbsquareOp<number> = a => a * a
export const reciprocal: ReciprocalOp<number> = a => 1 / a
export const divide: DivideOp<number> = (a, b) => a / b
export const divideByReal = divide
export const conservativeSqrt: FnConservativeSqrt<number> = (a) => isNaN(a) ? NaN : Math.sqrt(a)
const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a)
export const conservativeSqrt: ConservativeSqrtOp<number> = basicSqrt
export const sqrt =
(dep: {
config: Config,
complex: FnComplexBinary<number>
}): FnSqrt<number> => {
if (dep.config.predictable || !dep.complex) return conservativeSqrt
(dep: configDependency & Depends<ComplexOp<number>>): SqrtOp<number> => {
if (dep.config.predictable || !dep.complex) return basicSqrt
return a => {
if (isNaN(a)) return NaN
if (a >= 0) return Math.sqrt(a)

View File

@ -1,4 +1,4 @@
import type { FnIsReal, FnIsSquare } from "../interfaces/arithmetic"
import type { IsRealOp, IsSquareOp } from '../interfaces/predicate.js'
export const isReal: FnIsReal<number> = (a) => true
export const isSquare: FnIsSquare<number> = (a) => a >= 0
export const isReal: IsRealOp<number> = (a) => true
export const isSquare: IsSquareOp<number> = (a) => a >= 0

View File

@ -1,12 +1,12 @@
import {Config} from '../core/Config.js'
import type { FnEqual, FnUnequal } from '../interfaces/relational.js'
import type {EqualOp} from '../interfaces/relational.js'
const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16
export const equal =
(dep: {
config: Config
}): FnEqual<number> => (x, y) => {
}): EqualOp<number> => (x, y) => {
const eps = dep.config.epsilon
if (eps === null || eps === undefined) return x === y
if (x === y) return true
@ -20,8 +20,3 @@ export const equal =
return false
}
export const unequal = (dep: {
equal: FnEqual<number>
}): FnUnequal<number> =>
(x, y) => !dep.equal(x, y)

View File

@ -1,4 +1,4 @@
import type { FnNaN, FnOne, FnZero, FnRe } from "../interfaces/arithmetic"
import type { OneOp, ZeroOp, NanOp, ReOp } from '../interfaces/type.js'
export const number_type = {
before: ['Complex'],
@ -6,7 +6,21 @@ export const number_type = {
from: { string: (s: string) => +s }
}
export const zero: FnZero<number> = (a) => 0
export const one: FnOne<number> = (a) => 1
export const nan: FnNaN<number> = (a) => NaN
export const re: FnRe<number, number> = (a) => a
declare module "../interfaces/type" {
interface AssociatedTypes<T> {
numbers: {
type: number
zero: 0
one: 1
nan: typeof NaN
real: number
}
}
}
// I don't like the redundancy of repeating 'zero' and 'ZeroOp', any
// way to eliminate that?
export const zero: ZeroOp<number> = (a) => 0
export const one: OneOp<number> = (a) => 1
export const nan: NanOp<number> = (a) => NaN
export const re: ReOp<number> = (a) => a