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.
This commit is contained in:
Glen Whitney 2022-12-06 20:21:05 -05:00
parent 2a9039ac67
commit ccc6153786
14 changed files with 100 additions and 123 deletions

1
src/Complex/all.ts Normal file
View File

@ -0,0 +1 @@
export * as Complex from './native.js'

1
src/Complex/native.ts Normal file
View File

@ -0,0 +1 @@
export * from './type.js'

30
src/Complex/type.ts Normal file
View File

@ -0,0 +1,30 @@
/// <reference path="../numbers/type.ts">
import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js'
export type Complex<T> = {re: T; im: T;}
export const Complex_type = {
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),
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)})
}
}
export const complex_1 = <T>(dep: Dependency<'zero', [T]>) =>
(t: T) => ({re: t, im: dep.zero(t)})
export const complex_2 = <T>(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
}
}

2
src/all.ts Normal file
View File

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

View File

@ -1,33 +0,0 @@
import {Specifications, joinTypes, typeOfDependency} from '../core/Dispatcher'
export type Complex<T> = {re: T; im: T;}
declare module 'Dispatcher' {
namespace Specifications {
export class ComplexSpecifications {}
namespace ComplexSpecifications {
export class Complex_type<T> {
static test = (testT: (z: unknown) => z is T) =>
(z: unknown): z is Complex<T> =>
typeof z === 'object'
&& 're' in z && 'im' in z
&& testT(z.re) && testT(z.im);
static infer = (dep: typeOfDependency) =>
(z: Complex<unknown>) =>
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: <U>(convert: (from: U) => T) =>
(z: Complex<U>) => ({re: convert(z.re), im: convert(z.im)})
};
}
export const complex_1 = <T>(dep: DependencyType<'zero', [T]>) =>
(t: T) => ({re: t, im: dep.zero(t)})
export const complex_2 = <T>(t: T, u: T) => ({re: t, im: u})
}
}
}
export {Specifications}

View File

@ -2,6 +2,6 @@ export type Config = {
predictable: boolean predictable: boolean
} }
export type ConfigDependency = { export type configDependency = {
config: Config config: Config
} }

View File

@ -5,11 +5,13 @@
* for specific types (including their own). * for specific types (including their own).
*/ */
// First helper types and functions for the Dispatcher
type TypeName = string type TypeName = string
type Parameter = TypeName type Parameter = TypeName
type Signature = Parameter[] type Signature = Parameter[]
export class Specifications {} export interface ImplementationTypes {}
export type typeOfDependency = {typeOf: (x: unknown) => TypeName} export type typeOfDependency = {typeOf: (x: unknown) => TypeName}
//dummy implementation for now //dummy implementation for now
@ -18,64 +20,46 @@ export function joinTypes(a: TypeName, b: TypeName) {
return 'any' 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 * Build up to Dependency type lookup
// checking the types. */
type DependenciesType = Record<string, Function>
// Some relevant stuff that worked in the playground:
// type KeysMatching<T, N, V> = {[K in keyof T]-?: T[K] extends V ? K extends `${string}${N}${string}` ? K : never : never}[keyof T];
// type ValuesMatching<T, V> = {[K in keyof T]: T[K] extends V ? T[K] : never}[keyof T]
// type SubKeysMatching<T, N, V> = {[K in keyof T]: KeysMatching<T[K], N, V>}[keyof T]
// type SubValuesMatching<T, V> = {[K in keyof T]: ValuesMatching<T[K], V>}[keyof T]
// let trial: SubKeysMatching<typeof Bar, 'ng', number | boolean> = 'strange'
// let valtrial: SubValuesMatching<typeof Bar, number> = 3
// type MyFunc = (...args: [string, number]) => any
// Selecting the proper key for arguments [string, number] is working
// let key: KeysMatching<typeof Foo, 'a', MyFunc > = 'bar' // OK, and 'baz' here does fail, as desired
// The above should have all of the ingredients.
type DependenciesType = Record<String, Function>
type FinalShape<FuncType> = type FinalShape<FuncType> =
FuncType extends (arg: DependenciesType) => Function FuncType extends (arg: DependenciesType) => Function
? ReturnType<FuncType> : FuncType ? ReturnType<FuncType> : FuncType
type BeginsWith<Name> = `${Name}${string}` type BeginsWith<Name extends string> = `${Name}${string}`
type ImmediateDependency<Ob, Name, ParamTuple> = type DependencyTypes<Ob, Name extends string, Params extends unknown[]> =
{[K in keyof Ob]: K extends BeginsWith<Name> {[K in keyof Ob]: K extends BeginsWith<Name>
? FinalShape<Ob[K]> extends (...args: ParamTuple) => any ? FinalShape<Ob[K]> extends (...args: Params) => any
? FinalShape<Ob[K]> ? FinalShape<Ob[K]>
: never : never
: never}[keyof Ob] : never}
export type Dependency<Name extends string, Params extends unknown[]> =
{[N in Name]:
DependencyTypes<ImplementationTypes, N, Params>[keyof ImplementationTypes]}
type SpecType = typeof Specifications // Now types used in the Dispatcher class itself
export type ImplementationDependency<Name, ParamTuple> =
{[S in keyof SpecType]:
ImmediateDependency<SpecType[S], Name, ParamTuple>}[keyof SpecType]
type TypeSpecification = { type TypeSpecification = {
before?: TypeName[], before?: TypeName[],
test: ((x: unknown) => boolean) test: ((x: unknown) => boolean)
| ((d: DependenciesType) => (x: unknown) => boolean), | ((d: DependenciesType) => (x: unknown) => boolean),
from: Record<Typename, Function>, from: Record<TypeName, Function>,
infer?: (d: DependenciesType) => (z: unknown) => TypeName infer?: (d: DependenciesType) => (z: unknown) => TypeName
} }
type SpecObject = Record<string, Function | TypeSpecification> type SpecObject = Record<string, Function | TypeSpecification>
export type SpecifcationsGroup = Record<string, SpecObject> type SpecificationsGroup = Record<string, SpecObject>
export class Dispatcher { export class Dispatcher {
installSpecification( installSpecification(
name: string, name: string,
signature: Signature, signature: Signature,
returns: Type, returns: TypeName,
dependencies: Record<string, Signature>, dependencies: Record<string, Signature>,
behavior: Function // possible todo: constrain this type based behavior: Function // possible todo: constrain this type based
// on the signature, return type, and dependencies. Not sure if // on the signature, return type, and dependencies. Not sure if
@ -89,19 +73,21 @@ export class Dispatcher {
//TODO: implement me //TODO: implement me
} }
constructor(collection: SpecificationsGroup) { constructor(collection: SpecificationsGroup) {
for (key in collection) { for (const key in collection) {
console.log('Working on', key) console.log('Working on', key)
for (identifier in collection[key]) { for (const identifier in collection[key]) {
console.log('Handling', key, ':', identifier) console.log('Handling', key, ':', identifier)
const parts = identifier.split('_') const parts = identifier.split('_')
if (parts[parts.length - 1] === 'type') { if (parts[parts.length - 1] === 'type') {
parts.pop() parts.pop()
const name = parts.join('_') const name = parts.join('_')
installType(name, collection[key][identifier]) this.installType(
name, collection[key][identifier] as TypeSpecification)
} else { } else {
const name = parts[0] const name = parts[0]
installSpecification( this.installSpecification(
name, ['dunno'], 'unsure', {}, collection[key][identifier]) name, ['dunno'], 'unsure', {},
collection[key][identifier] as Function)
} }
} }
} }

View File

@ -1,5 +1,4 @@
import Dispatcher from 'core/Dispatcher' import {Dispatcher} from './core/Dispatcher.js'
import Complex from 'complex/type' import * as Specifications from './all.js'
import Specifications from 'number/arithmetic'
export default new Dispatcher(Specifications) export default new Dispatcher(Specifications)

View File

@ -1,29 +0,0 @@
import Specifications from './type'
import configDependency from '../core/Config'
/// <reference path="../complex/type.ts">
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}

View File

@ -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}

1
src/numbers/all.ts Normal file
View File

@ -0,0 +1 @@
export * as numbers from './native.js'

21
src/numbers/arithmetic.ts Normal file
View File

@ -0,0 +1,21 @@
/// <reference path="../Complex/type.ts">
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)))
}
}

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

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

13
src/numbers/type.ts Normal file
View File

@ -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
}
}