refactor: change Dispatcher class to a closure

Also starts work on typing that closure. Gets the property names right,
  but currently has the "unfilled" method types, rather than the returned
  "filled-in" function types. Not sure how to fix this.
This commit is contained in:
Glen Whitney 2023-10-18 23:10:58 -07:00
parent 6bfd06cafb
commit 569079e908
4 changed files with 103 additions and 44 deletions

View File

@ -6,6 +6,7 @@
*/ */
import {parseReflectedType, ImplementationDef} from './parseReflectedType.js' import {parseReflectedType, ImplementationDef} from './parseReflectedType.js'
import type {ValueIntersectionByKeyUnion} from '../interfaces/type.js'
// First helper types and functions for the Dispatcher // First helper types and functions for the Dispatcher
@ -35,32 +36,57 @@ type TypeSpecification = {
infer?: (d: DependenciesType) => (z: unknown) => TypeName infer?: (d: DependenciesType) => (z: unknown) => TypeName
} }
type SpecObject = Record<string, Function | TypeSpecification> type Callable = (...args: any) => any
type SpecObject = Record<string, Callable | TypeSpecification>
type SpecificationsGroup = Record<string, SpecObject> type SpecificationsGroup = Record<string, SpecObject>
export class Dispatcher { // Find all of the keys of an object that are functions but not aliases
installSpecification( // to another operation
name: string, type BaseOperations<Obj extends SpecObject> =
defn: ImplementationDef, {[K in keyof Obj]:
behavior: Function // possible todo: constrain this type based Obj[K] extends Callable
// on the signature, return type, and dependencies. Not sure if ? ReturnType<Obj[K]>['aliasOf'] extends (string | undefined)
// that's really possible, though. ? never
) { : K extends string ? K : never
console.log('Pretending to install', name, 'with signatures') : never
for (const signature of defn.fn.signatures) { }[keyof Obj]
console.log(' ', signature.args, '=>', signature.returns) // Gather all of the operations specified in a SpecificationsGroup
} type DispatcherOperations<G extends SpecificationsGroup> =
if (defn.fn.aliasOf) { {[K in keyof G]: BaseOperations<G[K]>}[keyof G]
console.log(' As an alias of', defn.fn.aliasOf) // Get the type of a given operation in a SpecObject:
} type BaseOperationSignature<
//TODO: implement me Obj extends SpecObject,
} Name extends BaseOperations<Obj>
installType(name: TypeName, typespec: TypeSpecification) { > = ValueIntersectionByKeyUnion<
console.log('Pretending to install type', name, typespec) {[K in keyof Obj]:
//TODO: implement me K extends Name
} ? Obj[K]
constructor(collection: SpecificationsGroup) { : Obj[K] extends Callable
? ReturnType<Obj[K]>['aliasOf'] extends (Name | undefined)
? Obj[K] : unknown
: unknown
},
keyof Obj>
// Get the type of a given operation in a SpecificationsGroup
type OperationSignature<
G extends SpecificationsGroup,
Name extends DispatcherOperations<G>
> = ValueIntersectionByKeyUnion<
{[K in keyof G]:
Name extends BaseOperations<G[K]>
? BaseOperationSignature<G[K], Name>
: unknown},
keyof G>
// Put it all together into the typing of the dispatcher
type DispatcherInterface<G extends SpecificationsGroup> =
{[Op in DispatcherOperations<G>]: OperationSignature<G, Op>}
export function createDispatcher<G extends SpecificationsGroup>(
collection: G): DispatcherInterface<G>
{
const implementations = [] const implementations = []
const types = {} // who knows what the type of this will be
const behaviors = {} // ditto
for (const key in collection) { for (const key in collection) {
console.log('Working on', key) console.log('Working on', key)
for (const identifier in collection[key]) { for (const identifier in collection[key]) {
@ -69,16 +95,44 @@ export class Dispatcher {
implementations.push([key, identifier, item]) implementations.push([key, identifier, item])
} else { } else {
console.log('Handling type', key, ':', identifier) console.log('Handling type', key, ':', identifier)
this.installType( installType( // In this design, installType would modify
item.name, collection[key][identifier] as TypeSpecification) // types by side effect; maybe Jos prefers some other
// factoring of this?
types, item.name,
collection[key][identifier] as TypeSpecification)
} }
} }
} }
for (const trio of implementations) { for (const trio of implementations) {
const [k, id, imp] = trio const [k, id, imp] = trio
console.log('Handling implementation', id, 'from', k) console.log('Handling implementation', id, 'from', k)
this.installSpecification( installSpecification(
id, parseReflectedType(id, imp.reflectedType), imp) behaviors, id, parseReflectedType(id, imp.reflectedType), imp)
}
} }
return {} as DispatcherInterface<G>
}
function installType(
types: Object, name: TypeName, typespec: TypeSpecification)
{
console.log('Pretending to install type', name, typespec)
//TODO: implement me
}
function installSpecification(
behaviors: Object, // Same issue as mentioned above with side effects...
name: string,
defn: ImplementationDef,
specification: Function // possible todo: constrain this type based
// on the signature, return type, and dependencies. Not sure if
// that's really possible, though.
) {
console.log('Pretending to install', name, 'with signatures')
for (const signature of defn.fn.signatures) {
console.log(' ', signature.args, '=>', signature.returns)
}
if (defn.fn.aliasOf) {
console.log(' As an alias of', defn.fn.aliasOf)
}
//TODO: implement me
} }

View File

@ -1,8 +1,12 @@
import {inspect} from 'node:util' import {inspect} from 'node:util'
import {Dispatcher} from './core/Dispatcher.js' import {createDispatcher} from './core/Dispatcher.js'
import * as Specifications from './all.js' import * as Specifications from './all.js'
export default new Dispatcher(Specifications) export Specifications
const math = createDispatcher(Specifications)
export default math
console.log('Made', math.add}
import {Complex} from './Complex/type.js' import {Complex} from './Complex/type.js'
import {absquare as absquare_complex} from './Complex/arithmetic.js' import {absquare as absquare_complex} from './Complex/arithmetic.js'

View File

@ -15,7 +15,7 @@ import {$$typeToString} from 'ts-macros'
* but that's OK, the generic parameter doesn't hurt in those cases. * but that's OK, the generic parameter doesn't hurt in those cases.
****/ ****/
type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = { export type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
[P in TKey]: (k: T[P])=>void [P in TKey]: (k: T[P])=>void
} [TKey] extends ((k: infer I)=>void) ? I : never } [TKey] extends ((k: infer I)=>void) ? I : never

View File

@ -4,6 +4,7 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./build", "outDir": "./build",
"moduleResolution": "nodenext", "moduleResolution": "nodenext",
"declaration": true,
"plugins": [ { "plugins": [ {
"transform": "ts-macros/dist/type-resolve", "transform": "ts-macros/dist/type-resolve",
"transformProgram": true "transformProgram": true