From ccc6153786aec6aac19dccec12a49a485b7687a6 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Tue, 6 Dec 2022 20:21:05 -0500 Subject: [PATCH] feat: TypeScript typings for Dispatcher implementations A first pass at specifying some implementations in TypeScript that actually compiles. It doesn't do anything, as installing types and operation specifications are currently dummy operations, but they are all invoked. --- src/Complex/all.ts | 1 + src/Complex/native.ts | 1 + src/Complex/type.ts | 30 ++++++++++++++++++ src/all.ts | 2 ++ src/complex/type.ts | 33 -------------------- src/core/Config.ts | 2 +- src/core/Dispatcher.ts | 66 +++++++++++++++------------------------ src/index.ts | 5 ++- src/number/arithmetic.ts | 29 ----------------- src/number/type.ts | 17 ---------- src/numbers/all.ts | 1 + src/numbers/arithmetic.ts | 21 +++++++++++++ src/numbers/native.ts | 2 ++ src/numbers/type.ts | 13 ++++++++ 14 files changed, 100 insertions(+), 123 deletions(-) create mode 100644 src/Complex/all.ts create mode 100644 src/Complex/native.ts create mode 100644 src/Complex/type.ts create mode 100644 src/all.ts delete mode 100644 src/complex/type.ts delete mode 100644 src/number/arithmetic.ts delete mode 100644 src/number/type.ts create mode 100644 src/numbers/all.ts create mode 100644 src/numbers/arithmetic.ts create mode 100644 src/numbers/native.ts create mode 100644 src/numbers/type.ts diff --git a/src/Complex/all.ts b/src/Complex/all.ts new file mode 100644 index 0000000..a386daa --- /dev/null +++ b/src/Complex/all.ts @@ -0,0 +1 @@ +export * as Complex from './native.js' diff --git a/src/Complex/native.ts b/src/Complex/native.ts new file mode 100644 index 0000000..6a91ee7 --- /dev/null +++ b/src/Complex/native.ts @@ -0,0 +1 @@ +export * from './type.js' diff --git a/src/Complex/type.ts b/src/Complex/type.ts new file mode 100644 index 0000000..0d5af74 --- /dev/null +++ b/src/Complex/type.ts @@ -0,0 +1,30 @@ +/// +import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js' + +export type Complex = {re: T; im: T;} + +export const Complex_type = { + test: (dep: {testT: (z: unknown) => z is T}) => + (z: unknown): z is Complex => + typeof z === 'object' && 're' in z && 'im' in z + && dep.testT(z.re) && dep.testT(z.im), + infer: (dep: typeOfDependency) => + (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), + from: { + T: (dep: Dependency<'zero', [T]>) => (t: T) => + ({re: t, im: dep.zero(t)}), + Complex: (dep: {convert: (from: U) => T}) => + (z: Complex) => ({re: dep.convert(z.re), im: dep.convert(z.im)}) + } +} + +export const complex_1 = (dep: Dependency<'zero', [T]>) => + (t: T) => ({re: t, im: dep.zero(t)}) +export const complex_2 = (t: T, u: T) => ({re: t, im: u}) + +declare module "../core/Dispatcher" { + interface ImplementationTypes { + complex_1_Complex: typeof complex_1 + complex_2_Complex: typeof complex_2 + } +} diff --git a/src/all.ts b/src/all.ts new file mode 100644 index 0000000..192c7be --- /dev/null +++ b/src/all.ts @@ -0,0 +1,2 @@ +export * from './numbers/all.js' +export * from './Complex/all.js' diff --git a/src/complex/type.ts b/src/complex/type.ts deleted file mode 100644 index d41c06b..0000000 --- a/src/complex/type.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {Specifications, joinTypes, typeOfDependency} from '../core/Dispatcher' - -export type Complex = {re: T; im: T;} - -declare module 'Dispatcher' { - namespace Specifications { - export class ComplexSpecifications {} - - namespace ComplexSpecifications { - export class Complex_type { - static test = (testT: (z: unknown) => z is T) => - (z: unknown): z is Complex => - typeof z === 'object' - && 're' in z && 'im' in z - && testT(z.re) && testT(z.im); - static infer = (dep: typeOfDependency) => - (z: Complex) => - joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)); - static from = { - T: (dep: ImplementationType<'zero', [T]>) => (t: T) => - ({re: t, im: dep.zero(t)}), - Complex: (convert: (from: U) => T) => - (z: Complex) => ({re: convert(z.re), im: convert(z.im)}) - }; - } - export const complex_1 = (dep: DependencyType<'zero', [T]>) => - (t: T) => ({re: t, im: dep.zero(t)}) - export const complex_2 = (t: T, u: T) => ({re: t, im: u}) - } - } -} - -export {Specifications} diff --git a/src/core/Config.ts b/src/core/Config.ts index 6ae5e58..3765328 100644 --- a/src/core/Config.ts +++ b/src/core/Config.ts @@ -2,6 +2,6 @@ export type Config = { predictable: boolean } -export type ConfigDependency = { +export type configDependency = { config: Config } diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 0d05933..1d13108 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -5,11 +5,13 @@ * for specific types (including their own). */ +// First helper types and functions for the Dispatcher + type TypeName = string type Parameter = TypeName type Signature = Parameter[] -export class Specifications {} +export interface ImplementationTypes {} export type typeOfDependency = {typeOf: (x: unknown) => TypeName} //dummy implementation for now @@ -18,64 +20,46 @@ export function joinTypes(a: TypeName, b: TypeName) { return 'any' } -// Will need to build this up. Need to start with looping through the keys of -// values of keys, and filtering ones that start with Name, then add in -// checking the types. - -// Some relevant stuff that worked in the playground: -// type KeysMatching = {[K in keyof T]-?: T[K] extends V ? K extends `${string}${N}${string}` ? K : never : never}[keyof T]; -// type ValuesMatching = {[K in keyof T]: T[K] extends V ? T[K] : never}[keyof T] - -// type SubKeysMatching = {[K in keyof T]: KeysMatching}[keyof T] -// type SubValuesMatching = {[K in keyof T]: ValuesMatching}[keyof T] - - -// let trial: SubKeysMatching = 'strange' -// let valtrial: SubValuesMatching = 3 - -// type MyFunc = (...args: [string, number]) => any - -// Selecting the proper key for arguments [string, number] is working -// let key: KeysMatching = 'bar' // OK, and 'baz' here does fail, as desired - -// The above should have all of the ingredients. -type DependenciesType = Record +/** + * Build up to Dependency type lookup + */ +type DependenciesType = Record type FinalShape = FuncType extends (arg: DependenciesType) => Function ? ReturnType : FuncType -type BeginsWith = `${Name}${string}` +type BeginsWith = `${Name}${string}` -type ImmediateDependency = +type DependencyTypes = {[K in keyof Ob]: K extends BeginsWith - ? FinalShape extends (...args: ParamTuple) => any + ? FinalShape extends (...args: Params) => any ? FinalShape : never - : never}[keyof Ob] + : never} + +export type Dependency = + {[N in Name]: + DependencyTypes[keyof ImplementationTypes]} -type SpecType = typeof Specifications - -export type ImplementationDependency = - {[S in keyof SpecType]: - ImmediateDependency}[keyof SpecType] +// Now types used in the Dispatcher class itself type TypeSpecification = { before?: TypeName[], test: ((x: unknown) => boolean) | ((d: DependenciesType) => (x: unknown) => boolean), - from: Record, + from: Record, infer?: (d: DependenciesType) => (z: unknown) => TypeName } type SpecObject = Record -export type SpecifcationsGroup = Record +type SpecificationsGroup = Record export class Dispatcher { installSpecification( name: string, signature: Signature, - returns: Type, + returns: TypeName, dependencies: Record, behavior: Function // possible todo: constrain this type based // on the signature, return type, and dependencies. Not sure if @@ -89,19 +73,21 @@ export class Dispatcher { //TODO: implement me } constructor(collection: SpecificationsGroup) { - for (key in collection) { + for (const key in collection) { console.log('Working on', key) - for (identifier in collection[key]) { + for (const identifier in collection[key]) { console.log('Handling', key, ':', identifier) const parts = identifier.split('_') if (parts[parts.length - 1] === 'type') { parts.pop() const name = parts.join('_') - installType(name, collection[key][identifier]) + this.installType( + name, collection[key][identifier] as TypeSpecification) } else { const name = parts[0] - installSpecification( - name, ['dunno'], 'unsure', {}, collection[key][identifier]) + this.installSpecification( + name, ['dunno'], 'unsure', {}, + collection[key][identifier] as Function) } } } diff --git a/src/index.ts b/src/index.ts index 3df603f..bb83486 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ -import Dispatcher from 'core/Dispatcher' -import Complex from 'complex/type' -import Specifications from 'number/arithmetic' +import {Dispatcher} from './core/Dispatcher.js' +import * as Specifications from './all.js' export default new Dispatcher(Specifications) diff --git a/src/number/arithmetic.ts b/src/number/arithmetic.ts deleted file mode 100644 index 90fe70e..0000000 --- a/src/number/arithmetic.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Specifications from './type' -import configDependency from '../core/Config' -/// - -declare module 'Dispatcher' { - namespace Specifications { - namespace NumberSpecifications { - 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 - & ImplementationDependency<'complex', [number,number]>) => { - if (dep.config.predictable || !dep.complex) { - return (a: number) => isNaN(n) ? NaN : Math.sqrt(n) - } - return (a: number) => { - if (isNaN(n)) return NaN - if (n >= 0) return Math.sqrt(n) - return dep.complex(0, Math.sqrt(unaryMinus(n))) - } - } - } - } -} - -export {Specifications} diff --git a/src/number/type.ts b/src/number/type.ts deleted file mode 100644 index 4f775fe..0000000 --- a/src/number/type.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Specifications from '../core/Dispatcher' - -declare module 'Dispatcher' { - namespace Specifications { - export class NumberSpecifications {} - namespace NumberSpecifications { - 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 {Specifications} diff --git a/src/numbers/all.ts b/src/numbers/all.ts new file mode 100644 index 0000000..b71b4c3 --- /dev/null +++ b/src/numbers/all.ts @@ -0,0 +1 @@ +export * as numbers from './native.js' diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts new file mode 100644 index 0000000..f1facf1 --- /dev/null +++ b/src/numbers/arithmetic.ts @@ -0,0 +1,21 @@ +/// +import {configDependency} from '../core/Config.js' +import {Dependency, ImplementationTypes} from '../core/Dispatcher.js' + +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) => { + if (isNaN(a)) return NaN + if (a >= 0) return Math.sqrt(a) + return dep.complex(0, Math.sqrt(unaryMinus(a))) + } + } diff --git a/src/numbers/native.ts b/src/numbers/native.ts new file mode 100644 index 0000000..10cd111 --- /dev/null +++ b/src/numbers/native.ts @@ -0,0 +1,2 @@ +export * from './type.js' +export * from './arithmetic.js' diff --git a/src/numbers/type.ts b/src/numbers/type.ts new file mode 100644 index 0000000..4e51f38 --- /dev/null +++ b/src/numbers/type.ts @@ -0,0 +1,13 @@ +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 + +declare module "../core/Dispatcher" { + interface ImplementationTypes { + zero_numbers: typeof zero + } +}