Declare implementations and dependencies via standard interfaces for operations #8

Merged
glen merged 20 commits from approach4.6 into main 2023-01-22 01:34:57 +00:00
14 changed files with 189 additions and 174 deletions
Showing only changes of commit 072b2a1f79 - Show all commits

View File

@ -1,47 +1,45 @@
import {Complex, ComplexOp} from './type.js'
import {Complex} from './type.js'
import type {
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
Dependencies, OpType, OpReturns, RealType, ZeroType
} from '../interfaces/type.js'
import type {IsSquareOp, IsRealOp} from '../interfaces/predicate.js'
declare module "../interfaces/type" {
interface Operations<T> {
// TODO: Make Dispatcher collapse operations that start with the same
// prefix up to a possible `_`
add_real: {params: [T, RealType<T>], returns: T}
divide_real: {params: [T, RealType<T>], returns: T}
}
}
export const add =
<T>(dep: Depends<AddOp<T>> & Depends<ComplexOp<T>>): AddOp<Complex<T>> =>
<T>(dep: Dependencies<'add' | 'complex', T>): OpType<'add', Complex<T>> =>
(w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im))
export const addReal =
<T>(dep: Depends<AddRealOp<T>> & Depends<ComplexOp<T>>):
AddRealOp<Complex<T>> =>
(z, r) => dep.complex(dep.addReal(z.re, r), z.im)
export const add_real =
<T>(dep: Dependencies<'add_real' | 'complex', T>):
OpType<'add_real', Complex<T>> =>
(z, r) => dep.complex(dep.add_real(z.re, r), z.im)
export const unaryMinus =
<T>(dep: Depends<UnaryMinusOp<T>> & Depends<ComplexOp<T>>):
UnaryMinusOp<Complex<T>> =>
<T>(dep: Dependencies<'unaryMinus' | 'complex', T>):
OpType<'unaryMinus', Complex<T>> =>
z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
export const conj =
<T>(dep: Depends<UnaryMinusOp<T>>
& Depends<ConjOp<T>>
& Depends<ComplexOp<T>>):
ConjOp<Complex<T>> =>
<T>(dep: Dependencies<'unaryMinus' | 'conj' | 'complex', T>):
OpType<'conj', Complex<T>> =>
z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im))
export const subtract =
<T>(dep: Depends<SubtractOp<T>> & Depends<ComplexOp<T>>):
SubtractOp<Complex<T>> =>
<T>(dep: Dependencies<'subtract' | 'complex', T>):
OpType<'subtract', Complex<T>> =>
(w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im))
export const multiply =
<T>(dep: Depends<AddOp<T>>
& Depends<SubtractOp<T>>
& Depends<MultiplyOp<T>>
& Depends<ConjOp<T>>
& Depends<ComplexOp<T>>):
MultiplyOp<Complex<T>> =>
<T>(dep: Dependencies<
'add' | 'subtract' | 'multiply' | 'conj' | 'complex', T>):
OpType<'multiply', Complex<T>> =>
(w, z) => {
const mult = dep.multiply
const realpart = dep.subtract(
@ -52,69 +50,48 @@ export const multiply =
}
export const absquare =
<T>(dep: Depends<AddOp<RealType<T>>> & Depends<AbsquareOp<T>>):
AbsquareOp<Complex<T>> =>
<T>(dep: Dependencies<'absquare', T>
& Dependencies<'add', OpReturns<'absquare', T>>):
OpType<'absquare', Complex<T>> =>
z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
export const divideByReal =
<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))
<T>(dep: Dependencies<'divide_real' | 'complex', T>):
OpType<'divide_real', Complex<T>> =>
(z, r) => dep.complex(dep.divide_real(z.re, r), dep.divide_real(z.im, r))
export const reciprocal =
<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))
<T>(dep: Dependencies<'conj' | 'absquare' | 'divide_real', Complex<T>>):
OpType<'reciprocal', Complex<T>> =>
z => dep.divide_real(dep.conj(z), dep.absquare(z))
export const divide =
<T>(dep: Depends<MultiplyOp<Complex<T>>>
& Depends<ReciprocalOp<Complex<T>>>):
DivideOp<Complex<T>> =>
<T>(dep: Dependencies<'multiply' | 'reciprocal', Complex<T>>):
OpType<'divide', 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: 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 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
// return the NaN of the type.
return dep.nan(dep.complex(r))
}
export const sqrt =
<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>> =>
<T>(dep:
Dependencies<
'conservativeSqrt' | 'add' | 'unaryMinus' | 'equal', RealType<T>>
& Dependencies<'zero' | 'add_real', T>
& Dependencies<'complex', T | ZeroType<T>>
& Dependencies<'absquare' | 're' | 'divide_real', Complex<T>>
& {add_complex_real: OpType<'add_real', Complex<T>>}):
OpType<'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 negr = dep.unaryMinus(r)
if (dep.equal(myabs, negr)) {
// pure imaginary square root; z.im already sero
return dep.complex(
dep.zero(z.re), dep.add_real(z.im, dep.conservativeSqrt(negr)))
}
const num = dep.add_complex_real(z, myabs)
const denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r))
const denom = dep.conservativeSqrt(denomsq)
return dep.divideByReal(num, denom)
return dep.divide_real(num, denom)
}
export const conservativeSqrt = sqrt

View File

@ -1,12 +1,9 @@
import {Complex} from './type.js'
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'
import type {Dependencies, OpType} from '../interfaces/type.js'
export const isReal =
<T>(dep: Depends<AddOp<T>> & Depends<EqualOp<T>> & Depends<IsRealOp<T>>):
IsRealOp<Complex<T>> =>
<T>(dep: Dependencies<'add' | 'equal' | 'isReal', T>):
OpType<'isReal', Complex<T>> =>
z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
export const isSquare: IsSquareOp<Complex<any>> = z => true // FIXME: not correct for Complex<bigint> once we get there
export const isSquare: OpType<'isSquare', Complex<any>> = z => true // FIXME: not correct for Complex<bigint> once we get there

View File

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

View File

@ -2,7 +2,7 @@ import {
joinTypes, typeOfDependency, Dependency,
} from '../core/Dispatcher.js'
import type {
OneOp, ZeroOp, NanOp, ReOp, ZeroType, OneType, NaNType, Depends
ZeroType, OneType, NaNType, Dependencies, OpType, OpReturns
} from '../interfaces/type.js'
export type Complex<T> = { re: T; im: T; }
@ -32,31 +32,34 @@ declare module "../interfaces/type" {
real: RealType<R>
} : never
}
interface Operations<T> {
complex: {params: [T] | [T,T], returns: Complex<T>}
}
}
export type ComplexOp<T> = {op?: 'complex', (a: T, b?: T): Complex<T>}
export const complex =
<T>(dep: Depends<ZeroOp<T>>): ComplexOp<T | ZeroType<T>> =>
<T>(dep: Dependencies<'zero', T>): OpType<'complex', T | ZeroType<T>> =>
(a, b) => ({re: a, im: b || dep.zero(a)})
export const zero =
<T>(dep: Depends<ZeroOp<T>> & Depends<ComplexOp<ZeroType<T>>>):
ZeroOp<Complex<T>> =>
<T>(dep: Dependencies<'zero', T>
& Dependencies<'complex', OpReturns<'zero', T>>):
OpType<'zero', Complex<T>> =>
z => dep.complex(dep.zero(z.re), dep.zero(z.im))
export const one =
<T>(dep: Depends<OneOp<T>>
& Depends<ZeroOp<T>>
& Depends<ComplexOp<ZeroType<T>|OneType<T>>>):
OneOp<Complex<T>> =>
<T>(dep: Dependencies<'one' | 'zero', T>
& Dependencies<'complex', OpReturns<'one' | 'zero', T>>):
OpType<'one', Complex<T>> =>
z => dep.complex(dep.one(z.re), dep.zero(z.im))
export const nan =
<T>(dep: Depends<NanOp<T>> & Depends<ComplexOp<NaNType<T>>>):
NanOp<Complex<T>> =>
<T>(dep: Dependencies<'nan', T>
& Dependencies<'complex', OpReturns<'nan', T>>):
OpType<'nan', Complex<T>> =>
z => dep.complex(dep.nan(z.re), dep.nan(z.im))
export const re =
<T>(dep: Depends<ReOp<T>>): ReOp<Complex<T>> =>
<T>(dep: Dependencies<'re', T>): OpType<'re', Complex<T>> =>
z => dep.re(z.re)

View File

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

View File

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

View File

@ -1,25 +1,25 @@
import type {Complex} from '../Complex/type.js'
import type {RealType, WithConstants, NaNType} from './type.js'
// 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 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>
type UnaryOperator<T> = {params: [T], returns: T}
type BinaryOperator<T> = {params: [T, T], returns: T}
declare module "./type" {
interface Operations<T> {
add: BinaryOperator<T>
unaryMinus: UnaryOperator<T>
conj: UnaryOperator<T>
subtract: BinaryOperator<T>
multiply: BinaryOperator<T>
square: UnaryOperator<T>
absquare: {params: [T], returns: RealType<T>}
reciprocal: UnaryOperator<T>
divide: BinaryOperator<T>
conservativeSqrt: UnaryOperator<T>
sqrt: {
params: [T],
returns: T extends Complex<infer R>
? Complex<R | ZeroType<R>>
: T | Complex<T>
}
}
}
export type SquareOp<T> = {op?: 'square', (z: T): T}

View File

@ -1,2 +1,9 @@
export type IsRealOp<T> = {op?: 'isReal', (a: T): boolean}
export type IsSquareOp<T> = {op?: 'isSquare', (a: T): boolean}
// Warning: a module must have something besides just a "declare module"
// section; otherwise it is ignored.
export type UnaryPredicate<T> = {params: [T], returns: boolean}
declare module "./type" {
interface Operations<T> {
isReal: UnaryPredicate<T>
isSquare: UnaryPredicate<T>
}
}

View File

@ -1,2 +1,9 @@
export type EqualOp<T> = {op?: 'equal', (a: T, b: T): boolean}
export type UnequalOp<T> = {op?: 'unequal', (a: T, b: T): boolean}
// Warning: a module must have something besides just a "declare module"
// section; otherwise it is ignored.
export type BinaryPredicate<T> = {params: [T, T], returns: boolean}
declare module "./type" {
interface Operations<T> {
equal: BinaryPredicate<T>
unequal: BinaryPredicate<T>
}
}

View File

@ -1,3 +1,15 @@
// Every typocomath type has some associated types; they need
// to be published as 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. Note the interface
// is generic with one parameter, corresponding to the fact that
// typocomath currently only allows types with a single generic parameter.
// This way, AssociatedTypes<SubType> can give the associated types
// for a generic type instantiated with SubType. That's not necessary for
// the 'undefined' type (or if you look in the `numbers` subdirectory,
// the 'number' type either) or any concrete type, but that's OK, the
// generic parameter doesn't hurt in those cases.
export interface AssociatedTypes<T> {
undefined: {
type: undefined
@ -9,24 +21,46 @@ export interface AssociatedTypes<T> {
}
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
export type Lookup<T, Name extends AssociatedTypeNames> = {
type ALookup<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 ZeroType<T> = ALookup<T, 'zero'>
export type OneType<T> = ALookup<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 NaNType<T> = ALookup<T, 'nan'>
export type RealType<T> = ALookup<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>}
// The global signature patterns for all operations need to be published in the
// following interface. Each key is the name of an operation (but note that
// the Dispatcher will automatically merge operations that have the same
// name when the first underscore `_` and everything thereafter is stripped).
// The type of each key should be an interface with two properties: 'params'
// whose type is the type of the parameter list for the operation, and
// 'returns' whose type is the return type of the operation on those
// parameters. These types are generic in a parameter type T which should
// be interpreted as the type that the operation is supposed to "primarily"
// operate on, although note that some of the parameters and/or return types
// may depend on T rather than be exactly T.
// So note that the example 're' below provides essentially the same
// information that e.g.
// `type ReOp<T> = (t: T) => RealType<T>`
// would, but in a way that is much easier to manipulate in TypeScript,
// and it records the name of the operation as 're' also by virtue of the
// key 're' in the interface.
export interface Operations<T> {
zero: {params: [WithConstants<T>], returns: ZeroType<T>}
one: {params: [WithConstants<T>], returns: OneType<T>}
nan: {params: [T | NaNType<T>], returns: NaNType<T>}
re: {params: [T], returns: RealType<T>}
}
type NamedFunction = {op?: string, (...params: any[]): any}
export type Depends<FuncType extends NamedFunction> =
{[K in FuncType['op']]: FuncType}
type OpKey = keyof Operations<unknown>
export type OpReturns<Name extends OpKey, T> = Operations<T>[Name]['returns']
export type OpType<Name extends OpKey, T> =
(...args: Operations<T>[Name]['params']) => OpReturns<Name, T>
export type Dependencies<Name extends OpKey, T> = {[K in Name]: OpType<K, T>}

View File

@ -1,27 +1,21 @@
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'
import type {Dependencies, OpType} from '../interfaces/type.js'
export const add: AddOp<number> = (a, b) => a + b
export const addReal = add
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 add: OpType<'add', number> = (a, b) => a + b
export const unaryMinus: OpType<'unaryMinus', number> = a => -a
export const conj: OpType<'conj', number> = a => a
export const subtract: OpType<'subtract', number> = (a, b) => a - b
export const multiply: OpType<'multiply', number> = (a, b) => a * b
export const absquare: OpType<'absquare', number> = a => a * a
export const reciprocal: OpType<'reciprocal', number> = a => 1 / a
export const divide: OpType<'divide', number> = (a, b) => a / b
const basicSqrt = a => isNaN(a) ? NaN : Math.sqrt(a)
export const conservativeSqrt: ConservativeSqrtOp<number> = basicSqrt
export const conservativeSqrt: OpType<'conservativeSqrt', number> = basicSqrt
export const sqrt =
(dep: configDependency & Depends<ComplexOp<number>>): SqrtOp<number> => {
(dep: configDependency & Dependencies<'complex', number>):
OpType<'sqrt', number> => {
if (dep.config.predictable || !dep.complex) return basicSqrt
return a => {
if (isNaN(a)) return NaN

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import type { OneOp, ZeroOp, NanOp, ReOp } from '../interfaces/type.js'
import type { OpType } from '../interfaces/type.js'
export const number_type = {
before: ['Complex'],
@ -18,9 +18,8 @@ declare module "../interfaces/type" {
}
}
// 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
// I don't like the redundancy of repeating 'zero'; any way to eliminate that?
export const zero: OpType<'zero', number> = (a) => 0
export const one: OpType<'one', number> = (a) => 1
export const nan: OpType<'nan', number> = (a) => NaN
export const re: OpType<'re', number> = (a) => a