Declare implementations and dependencies via standard interfaces for operations (#8)

Adds a new subdirectory `interfaces` where standard interfaces
  are defined. Additional interfaces for a given operation can
  be added with an `AliasOf` type operator. Provides type
  operators that give the return type, full function type, and
  the type of a dependency on, a given operator.

  Resolves #6.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
Reviewed-on: #8
This commit is contained in:
Glen Whitney 2023-01-22 01:34:57 +00:00
parent 3fa216d1f4
commit cc1e66c054
24 changed files with 397 additions and 72 deletions

View file

@ -1,8 +1,6 @@
import {ForType} from '../core/Dispatcher.js'
import * as Complex from './native.js'
import * as complex from './arithmetic.js'
export { complex }
export {Complex}
declare module "../core/Dispatcher" {
interface ImplementationTypes extends ForType<'Complex', typeof Complex> {}
}

101
src/Complex/arithmetic.ts Normal file
View file

@ -0,0 +1,101 @@
import {Complex} from './type.js'
import type {
Dependencies, Signature, Returns, RealType, AliasOf
} from '../interfaces/type.js'
declare module "../interfaces/type" {
interface Signatures<T> {
addReal: AliasOf<'add', (a: T, b: RealType<T>) => T>
divideReal: AliasOf<'divide', (a: T, b: RealType<T>) => T>
}
}
export const add =
<T>(dep: Dependencies<'add' | 'complex', T>): Signature<'add', Complex<T>> =>
(w, z) => dep.complex(dep.add(w.re, z.re), dep.add(w.im, z.im))
export const addReal =
<T>(dep: Dependencies<'addReal' | 'complex', T>):
Signature<'addReal', Complex<T>> =>
(z, r) => dep.complex(dep.addReal(z.re, r), z.im)
export const unaryMinus =
<T>(dep: Dependencies<'unaryMinus' | 'complex', T>):
Signature<'unaryMinus', Complex<T>> =>
z => dep.complex(dep.unaryMinus(z.re), dep.unaryMinus(z.im))
export const conj =
<T>(dep: Dependencies<'unaryMinus' | 'conj' | 'complex', T>):
Signature<'conj', Complex<T>> =>
z => dep.complex(dep.conj(z.re), dep.unaryMinus(z.im))
export const subtract =
<T>(dep: Dependencies<'subtract' | 'complex', T>):
Signature<'subtract', Complex<T>> =>
(w, z) => dep.complex(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im))
export const multiply =
<T>(dep: Dependencies<
'add' | 'subtract' | 'multiply' | 'conj' | 'complex', T>):
Signature<'multiply', 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 dep.complex(realpart, imagpart)
}
export const absquare =
<T>(dep: Dependencies<'absquare', T>
& Dependencies<'add', Returns<'absquare', T>>):
Signature<'absquare', Complex<T>> =>
z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
export const divideReal =
<T>(dep: Dependencies<'divideReal' | 'complex', T>):
Signature<'divideReal', Complex<T>> =>
(z, r) => dep.complex(dep.divideReal(z.re, r), dep.divideReal(z.im, r))
export const reciprocal =
<T>(dep: Dependencies<'conj' | 'absquare' | 'divideReal', Complex<T>>):
Signature<'reciprocal', Complex<T>> =>
z => dep.divideReal(dep.conj(z), dep.absquare(z))
export const divide =
<T>(dep: Dependencies<'multiply' | 'reciprocal', Complex<T>>):
Signature<'divide', Complex<T>> =>
(w, z) => dep.multiply(w, dep.reciprocal(z))
// The dependencies are slightly tricky here, because there are three types
// involved: Complex<T>, T, and RealType<T>, all of which might be different,
// and we have to get it straight which operations we need on each type, and
// in fact, we need `addReal` on both T and Complex<T>, hence the dependency
// with a custom name, not generated via Dependencies<...>
export const sqrt =
<T>(dep: Dependencies<'equal' | 'conservativeSqrt' | 'unaryMinus', RealType<T>>
& Dependencies<'zero' | 'complex', T>
& Dependencies<'absquare' | 're' | 'divideReal', Complex<T>>
& {
addTR: Signature<'addReal', T>,
addRR: Signature<'add', RealType<T>>,
addCR: Signature<'addReal', Complex<T>>
}):
Signature<'sqrt', Complex<T>> =>
z => {
const myabs = dep.conservativeSqrt(dep.absquare(z))
const r = dep.re(z)
const negr = dep.unaryMinus(r)
if (dep.equal(myabs, negr)) {
// pure imaginary square root; z.im already zero
return dep.complex(
dep.zero(z.re), dep.addTR(z.im, dep.conservativeSqrt(negr)))
}
const num = dep.addCR(z, myabs)
const denomsq = dep.addRR(dep.addRR(myabs, myabs), dep.addRR(r, r))
const denom = dep.conservativeSqrt(denomsq)
return dep.divideReal(num, denom)
}
export const conservativeSqrt = sqrt

9
src/Complex/predicate.ts Normal file
View file

@ -0,0 +1,9 @@
import {Complex} from './type.js'
import type {Dependencies, Signature} from '../interfaces/type.js'
export const isReal =
<T>(dep: Dependencies<'add' | 'equal' | 'isReal', T>):
Signature<'isReal', Complex<T>> =>
z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
export const isSquare: Signature<'isSquare', Complex<any>> = z => true // FIXME: not correct for Complex<bigint> once we get there

View file

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

View file

@ -1,22 +1,63 @@
import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js'
import {joinTypes, typeOfDependency} from '../core/Dispatcher.js'
import type {
ZeroType, OneType, NaNType, Dependencies, Signature, Returns
} from '../interfaces/type.js'
export type Complex<T> = {re: T; im: T;}
export type Complex<T> = { re: T; im: T; }
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> =>
typeof z === 'object' && 're' in z && 'im' in z
&& dep.testT(z.re) && dep.testT(z.im),
typeof z === 'object' && z != null && 're' in z && 'im' in z
&& dep.testT(z['re']) && dep.testT(z['im']),
infer: (dep: typeOfDependency) =>
(z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)),
from: {
T: <T>(dep: Dependency<'zero', [T]>) => (t: T) =>
({re: t, im: dep.zero(t)}),
Complex: <U,T>(dep: {convert: (from: U) => T}) =>
(z: Complex<U>) => ({re: dep.convert(z.re), im: dep.convert(z.im)})
T: <T>(dep: Dependencies<'zero', T>) => (t: T) =>
({ re: t, im: dep.zero(t) }),
Complex: <U, T>(dep: { convert: (from: U) => T }) =>
(z: Complex<U>) => ({ re: dep.convert(z.re), im: dep.convert(z.im) })
}
}
export const complex_unary = <T>(dep: Dependency<'zero', [T]>) =>
(t: T) => ({re: t, im: dep.zero(t)})
export const complex_binary = <T>(t: T, u: T) => ({re: t, im: u})
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
}
interface Signatures<T> {
complex: ((re: T) => Complex<T>) | ((re: T, im: T) => Complex<T>)
}
}
export const complex =
<T>(dep: Dependencies<'zero', T>): Signature<'complex', T> =>
(a, b) => ({re: a, im: b || dep.zero(a)})
export const zero =
<T>(dep: Dependencies<'zero', T>
& Dependencies<'complex', Returns<'zero', T>>):
Signature<'zero', Complex<T>> =>
z => dep.complex(dep.zero(z.re), dep.zero(z.im))
export const one =
<T>(dep: Dependencies<'one' | 'zero', T>
& Dependencies<'complex', Returns<'one' | 'zero', T>>):
Signature<'one', Complex<T>> =>
z => dep.complex(dep.one(z.re), dep.zero(z.im))
export const nan =
<T>(dep: Dependencies<'nan', T>
& Dependencies<'complex', Returns<'nan', T>>):
Signature<'nan', Complex<T>> =>
z => dep.complex(dep.nan(z.re), dep.nan(z.im))
export const re =
<T>(dep: Dependencies<'re', T>): Signature<'re', Complex<T>> =>
z => dep.re(z.re)