Compare commits

...

6 Commits

Author SHA1 Message Date
Glen Whitney f860fca03d fix: two files left out of last commit 2023-01-21 20:01:16 -05:00
Glen Whitney 7db6f38a30 refactor: Specify implementation types directly
Rather than speficying return types as a type transformation from
  parameter types, assume that all the type info can be
  inferred from the first parameter, and directly specify the
  implementation types. Vastly simplifies the declaration of
  implementation types.
2022-12-27 17:55:17 -05:00
Glen Whitney 8c06c8f36e feat: Add generic operation `square` and numeric `unequal` (#4)
Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #4
2022-12-22 16:12:36 +00:00
Glen Whitney fbec410c42 feat: Implement complex arithmetic through sqrt
Together with any auxiliary functions needed for that goal. Also
  strives to ensure the same functions are being defined for
  number and for `Complex<T>`.
2022-12-22 00:14:58 -05:00
Glen Whitney d55776655f refactor: Convenience type operator for specifying concrete signatures 2022-12-21 11:41:25 -05:00
Glen Whitney 1eb73be2fa refactor: entirely new scheme for specifying return types 2022-12-21 00:18:42 -05:00
21 changed files with 441 additions and 43 deletions

View File

@ -1,8 +1,10 @@
import {ForType} from '../core/Dispatcher.js'
import {ComplexImpTypes} from './type.js'
import * as Complex from './native.js'
export {Complex}
declare module "../core/Dispatcher" {
interface ImplementationTypes extends ForType<'Complex', typeof Complex> {}
interface ImpTypes<T>
extends ForType<'Complex', ComplexImpTypes<T>> {}
}

124
src/Complex/arithmetic.ts Normal file
View File

@ -0,0 +1,124 @@
import {Complex, UnderlyingReal, complex_binary} from './type.js'
import {Dependency, ImpType} from '../core/Dispatcher.js'
type ComplexUnary<T> =
T extends Complex<infer R> ? (a: Complex<R>) => Complex<R> : never
type ComplexBinary<T> =
T extends Complex<infer R>
? (a: Complex<R>, b: Complex<R>) => Complex<R>
: never
type ComplexReal<T> = T extends Complex<infer R>
? (a: Complex<R>, b: UnderlyingReal<R>) => Complex<R>
: never
declare module "./type" {
interface ComplexImpTypes<T> {
add: ComplexBinary<T>
add_real: ComplexReal<T>
unaryMinus: ComplexUnary<T>
conj: ComplexUnary<T>
subtract: ComplexBinary<T>
multiply: ComplexBinary<T>
absquare: T extends Complex<infer R> ? (a: T) => UnderlyingReal<R> : never
reciprocal: ComplexUnary<T>
divide: ComplexBinary<T>
divide_real: ComplexReal<T>
// square root that remains the same type
conservativeSqrt: ComplexUnary<T>
// Same as conservativeSqrt for complex numbers:
sqrt: ComplexUnary<T>
}
}
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 add_real =
<T>(dep: Dependency<'add_real', [T, UnderlyingReal<T>]>):
ImpType<'add_real', [Complex<T>, UnderlyingReal<T>]> =>
(z, r) => complex_binary(dep.add_real(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', [UnderlyingReal<T>, UnderlyingReal<T>]>):
ImpType<'absquare', [Complex<T>]> =>
z => dep.add(dep.absquare(z.re), dep.absquare(z.im))
export const divide_real =
<T>(dep: Dependency<'divide_real', [T, UnderlyingReal<T>]>):
ImpType<'divide_real', [Complex<T>, UnderlyingReal<T>]> =>
(z, r) => complex_binary(
dep.divide_real(z.re, r), dep.divide_real(z.im, r))
export const reciprocal =
<T>(dep: Dependency<'conj', [Complex<T>]>
& Dependency<'absquare', [Complex<T>]>
& Dependency<'divide_real', [Complex<T>, UnderlyingReal<T>]>):
ImpType<'reciprocal', [Complex<T>]> =>
z => dep.divide_real(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 sqrt =
<T>(dep: Dependency<'absquare' | 're', [Complex<T>]>
& Dependency<'conservativeSqrt' | 'unaryMinus', [UnderlyingReal<T>]>
& Dependency<'divide_real', [Complex<T>, UnderlyingReal<T>]>
& Dependency<'add_real', [T, UnderlyingReal<T>]>
& {add_complex_real:
ImpType<'add_real', [Complex<T>, UnderlyingReal<T>]>}
& Dependency<'equal' | 'add', [UnderlyingReal<T>, UnderlyingReal<T>]>
& Dependency<'complex', [T, T]>
& Dependency<'zero', [T]>
): ImpType<'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.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.divide_real(num, denom)
}
export const conservativeSqrt = sqrt

View File

@ -1 +1,4 @@
export * from './type.js'
export * from './arithmetic.js'
export * from './predicate.js'
export * from './relational.js'

21
src/Complex/predicate.ts Normal file
View File

@ -0,0 +1,21 @@
import {Complex} from './type.js'
import {Dependency, ImpType} from '../core/Dispatcher.js'
type ComplexPredicate<T> = T extends Complex<any> ? (a: T) => boolean : never
declare module "./type" {
interface ComplexImpTypes<T> {
isReal: ComplexPredicate<T>
isSquare: ComplexPredicate<T>
}
}
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

16
src/Complex/relational.ts Normal file
View File

@ -0,0 +1,16 @@
import {Complex} from './type.js'
import {ImpType, Dependency} from '../core/Dispatcher.js'
type ComplexRelation<T> =
T extends Complex<any> ? (a: T, b: T) => boolean : never
declare module "./type" {
interface ComplexImpTypes<T> {
equal: ComplexRelation<T>
}
}
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)

View File

@ -1,7 +1,12 @@
import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js'
import {
joinTypes, typeOfDependency, Dependency, ImpType, ImpReturns
} from '../core/Dispatcher.js'
export type Complex<T> = {re: T; im: T;}
export type UnderlyingReal<T> =
T extends Complex<infer U> ? UnderlyingReal<U> : T
export const Complex_type = {
test: <T>(dep: {testT: (z: unknown) => z is T}) =>
(z: unknown): z is Complex<T> =>
@ -17,6 +22,37 @@ export const Complex_type = {
}
}
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})
export interface ComplexImpTypes<T> {
complex: (a: T, b?: T) => Complex<T>
zero: T extends Complex<infer R>
? (a: Complex<R>) => Complex<R | ImpReturns<'zero', [R]>> : never
one: T extends Complex<infer R>
? (a: Complex<R>) => Complex<R | ImpReturns<'one' | 'zero', [R]>> : never
nan: T extends Complex<infer R>
? (a: Complex<R>) => Complex<R | ImpReturns<'NaN', [R]>> : never
re: T extends Complex<infer R>
? (a: Complex<R>) => UnderlyingReal<R> : 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)

View File

@ -1,2 +1,3 @@
export * from './numbers/all.js'
export * from './Complex/all.js'
export * from './generic/all.js'

View File

@ -1,4 +1,5 @@
export type Config = {
epsilon: number
predictable: boolean
}

View File

@ -9,15 +9,59 @@
type TypeName = string
type Parameter = TypeName
type Signature = Parameter[]
type InputSignature = Parameter[]
type DependenciesType = Record<string, Function>
export interface ImplementationTypes {}
export type typeOfDependency = {typeOf: (x: unknown) => TypeName}
// Helper for collecting implementations
// (Really just suffixes the type name onto the keys of exports)
export type ForType<T extends string, Exports> = keyof Exports extends string
? {[K in keyof Exports as `${K}_${T}`]: Exports[K]}
// All of the implementations must publish descriptions of their
// types into the following interface, using the format
// described just below:
export interface ImpTypes<T> {}
/*****
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 type of that implementation when T matches the
first parameter of the implementation.
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 ImpTypes<T> {
foo_example: (a: number, b: string) => boolean
}
}
```
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: (a: T) => Vector<T>
...
```
In practice, each subdirectory corresponding to a type, like Complex,
defines an interface, like `ComplexImpTypes<T>` 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.
Note that if the type is not generic, it will not bother with the generic
parameter in its subdirectory ImpTypes interface.
And note again, that for generic types, the type parameter of ImpTypes
_must_ match the type of the **first** argument of the operation. Hence,
choose argument order so that you can infer all the other parameter types
and the return type from the type of the first argument.
*****/
// 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
//dummy implementation for now
@ -26,27 +70,34 @@ export function joinTypes(a: TypeName, b: TypeName) {
return 'any'
}
/**
* Build up to Dependency type lookup
*/
type DependenciesType = Record<string, Function>
// Used to filter keys that match a given operation name
type BeginsWith<Name extends string> = Name | `${Name}_${string}`
type FinalShape<FuncType> =
FuncType extends (arg: DependenciesType) => Function
? ReturnType<FuncType> : FuncType
type BeginsWith<Name extends string> = `${Name}${string}`
type DependencyTypes<Ob, Name extends string, Params extends unknown[]> =
{[K in keyof Ob]: K extends BeginsWith<Name>
? FinalShape<Ob[K]> extends (...args: Params) => any
? FinalShape<Ob[K]>
: never
export type RawDependency<Name extends string, Params extends unknown[]> =
{[K in keyof ImpTypes<Params[0]>]: K extends BeginsWith<Name>
? ImpTypes<Params[0]>[K] extends (...args: Params) => any
? ImpTypes<Params[0]>[K]
: never
: never}
// The type of an implementation (with dependencies satisfied,
// based on its name and the parameters it takes
export type ImpType<Name extends string, Params extends unknown[]> =
RawDependency<Name, Params>[keyof ImpTypes<Params[0]>]
// 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[]> =
{[N in Name]:
DependencyTypes<ImplementationTypes, N, Params>[keyof ImplementationTypes]}
{[N in Name]: ImpType<N, Params>}
// Look up the return type of an implementation based on its name
// and the parameters it takes
export type ImpReturns<Name extends string, Params extends unknown[]> =
ReturnType<ImpType<Name, Params>>
// Now types used in the Dispatcher class itself
@ -64,9 +115,9 @@ type SpecificationsGroup = Record<string, SpecObject>
export class Dispatcher {
installSpecification(
name: string,
signature: Signature,
signature: InputSignature,
returns: TypeName,
dependencies: Record<string, Signature>,
dependencies: Record<string, InputSignature>,
behavior: Function // possible todo: constrain this type based
// on the signature, return type, and dependencies. Not sure if
// that's really possible, though.

10
src/generic/all.ts Normal file
View File

@ -0,0 +1,10 @@
import { ForType } from '../core/Dispatcher.js'
import { GenericImpTypes } from './type.js'
import * as generic from './native.js'
export { generic }
declare module "../core/Dispatcher" {
interface ImpTypes<T>
extends ForType<'generic', GenericImpTypes<T>> { }
}

12
src/generic/arithmetic.ts Normal file
View File

@ -0,0 +1,12 @@
import {Dependency, ImpType, ImpReturns} from '../core/Dispatcher.js'
declare module "./type" {
interface GenericImpTypes<T> {
square: (a: T) => ImpReturns<'multiply', [T, T]>
}
}
export const square =
<T>(dep: Dependency<'multiply', [T, T]>):
ImpType<'square', [T]> =>
t => dep.multiply(t, t)

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

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

11
src/generic/relational.ts Normal file
View File

@ -0,0 +1,11 @@
import {Dependency, ImpType} from '../core/Dispatcher.js'
declare module "./type" {
interface GenericImpTypes<T> {
unequal: (a: T, b:T) => boolean
}
}
export const unequal =
<T>(dep: Dependency<'equal', [T,T]>): ImpType<'unequal', [T, T]> =>
(a, b) => !dep.equal(a, b)

3
src/generic/type.ts Normal file
View File

@ -0,0 +1,3 @@
export interface GenericImpTypes<T> {
}

View File

@ -2,3 +2,19 @@ import {Dispatcher} from './core/Dispatcher.js'
import * as Specifications from './all.js'
export default new Dispatcher(Specifications)
import {Complex} from './Complex/type.js'
import {absquare as absquare_complex} from './Complex/arithmetic.js'
const mockRealAdd = (a: number, b: number) => a+b
const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im
const quatAbsquare = absquare_complex({
add: mockRealAdd,
absquare: mockComplexAbsquare
})
const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}})
const typeTest: typeof myabs = 7 // check myabs is just a number
console.log('Result is', myabs)

View File

@ -1,8 +1,10 @@
import {ForType} from '../core/Dispatcher.js'
import {NumbersImpTypes} from './type.js'
import * as numbers from './native.js'
export {numbers}
declare module "../core/Dispatcher" {
interface ImplementationTypes extends ForType<'numbers', typeof numbers> {}
interface ImpTypes<T>
extends ForType<'numbers', NumbersImpTypes> {}
}

View File

@ -1,18 +1,48 @@
import {configDependency} from '../core/Config.js'
import {Dependency} from '../core/Dispatcher.js'
import {
Dependency, ImpType
} from '../core/Dispatcher.js'
import type {Complex, UnderlyingReal} from '../Complex/type.js'
type UnaryNumber = (a: number) => number
type BinaryNumber = (a: number, b:number) => number
declare module "./type" {
interface NumbersImpTypes {
add: BinaryNumber
unaryMinus: UnaryNumber
conj: UnaryNumber
subtract: BinaryNumber
multiply: BinaryNumber
absquare: UnaryNumber
reciprocal: UnaryNumber
divide: BinaryNumber
conservativeSqrt: UnaryNumber
// 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: (a: number) => number | Complex<number>
}
}
export const add: ImpType<'add', [number, number]> = (a, b) => a + b
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 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 =
(dep: configDependency
& Dependency<'complex', [number, number]>) => {
if (dep.config.predictable || !dep.complex) {
return (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
}
return (a: number) => {
& Dependency<'complex', [number, number]>): ImpType<'sqrt', [number]> => {
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(unaryMinus(a)))

View File

@ -1,2 +1,4 @@
export * from './type.js'
export * from './arithmetic.js'
export * from './predicate.js'
export * from './relational.js'

12
src/numbers/predicate.ts Normal file
View File

@ -0,0 +1,12 @@
import {ImpType} from '../core/Dispatcher.js'
type NumberPredicate = (a: number) => boolean
declare module "./type" {
interface NumbersImpTypes {
isReal: NumberPredicate
isSquare: NumberPredicate
}
}
export const isReal: ImpType<'isReal', [number]> = a => true
export const isSquare: ImpType<'isSquare', [number]> = a => a >= 0

29
src/numbers/relational.ts Normal file
View File

@ -0,0 +1,29 @@
import {configDependency} from '../core/Config.js'
import {ImpType, Dependency} from '../core/Dispatcher.js'
const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16
type NumberRelation = (a: number, b: number) => boolean
declare module "./type" {
interface NumbersImpTypes {
equal: NumberRelation
}
}
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
}

View File

@ -1,7 +1,21 @@
import {ImpType} from '../core/Dispatcher.js'
import type {UnderlyingReal} from '../Complex/type.js'
export const number_type = {
before: ['Complex'],
test: (n: unknown): n is number => typeof n === 'number',
from: {string: s => +s}
}
export const zero = (a: number) => 0
export interface NumbersImpTypes {
zero: (a: number) => 0
one: (a: number) => 1
nan: (a: number) => typeof NaN
re: (a: number) => number
}
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