feat: Barest outline of a Dispatcher, providing only typeOf so far #11

Merged
glen merged 1 commits from feat/dispatch_type into main 2024-10-25 15:27:03 +00:00
3 changed files with 136 additions and 11 deletions

View File

@ -14,7 +14,9 @@ export const Complex_type = {
typeof z === 'object' && z != null && 're' in z && 'im' in z typeof z === 'object' && z != null && 're' in z && 'im' in z
&& dep.testT(z.re) && dep.testT(z.im), && dep.testT(z.re) && dep.testT(z.im),
infer: (dep: {typeOf: CommonSignature<undefined>['typeOf']}) => infer: (dep: {typeOf: CommonSignature<undefined>['typeOf']}) =>
(z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), (z: Complex<unknown>) => ({
T: joinTypes(dep.typeOf(z.re), dep.typeOf(z.im))
}),
from: { from: {
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) => Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
(z: Complex<U>): Complex<T> => (z: Complex<U>): Complex<T> =>

View File

@ -1,3 +1,4 @@
import {inspect} from 'node:util'
import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type' import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type'
// A base type that roughly describes the dependencies of a single factory // A base type that roughly describes the dependencies of a single factory
@ -102,10 +103,6 @@ type Implementations<
Specs extends Specifications<Signatures, NeedKeys, NeedList> Specs extends Specifications<Signatures, NeedKeys, NeedList>
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>} > = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
export interface ReflectedTypeInfo {
reflectedType5: string
}
// The builder interface that lets us assemble narrowly-typed Implementations: // The builder interface that lets us assemble narrowly-typed Implementations:
interface ImplementationBuilder< interface ImplementationBuilder<
Signatures extends GenSigs, Signatures extends GenSigs,
@ -139,7 +136,7 @@ interface ImplementationBuilder<
Specs & {[K in NewKeys]: DepCheck<RD, Signatures>} Specs & {[K in NewKeys]: DepCheck<RD, Signatures>}
> >
done(info?: ReflectedTypeInfo): Implementations<Signatures, NeedKeys, NeedList, Specs> & ReflectedTypeInfo done(): Implementations<Signatures, NeedKeys, NeedList, Specs>
} }
// And a function that actually provides the builder interface: // And a function that actually provides the builder interface:
@ -191,8 +188,8 @@ function impBuilder<
Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>} Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>}
> >
}, },
done(info?: ReflectedTypeInfo) { done() {
return { ...sofar, ...info } return sofar
} }
} }
} }
@ -202,3 +199,124 @@ export function implementations<Signatures extends GenSigs>(
): ImplementationBuilder<Signatures, never, {}, {}> { ): ImplementationBuilder<Signatures, never, {}, {}> {
return impBuilder({}) return impBuilder({})
} }
// Now we turn to creating a Dispatcher itself. For this we use loose types,
// and rely on the type annotations from our special build for runtime type
// identification.
type Callable = (...args: any) => any
type Implementation = {implementation: Callable}
type Factory = {factory: Callable, dependencies: Record<string, any>}
type Reflected = {_reflectedType5: any}
type TypeSpec = {
name: string,
before?: string[],
test: Callable,
from: Record<string, Callable>,
infer?: Callable
}
type ImpItem = Implementation | Factory
type ImpGroup = Record<string, ImpItem>
// When this is being compiled, TypeScript can't tell that the
// ImpSpecification entities will have been reflected:
type ImpSpecification = (ImpGroup | (() => ImpGroup) | TypeSpec) // & Reflected
interface ImpSpecs extends Record<string, ImpSpecification | ImpSpecs> {}
type ImpHolder = {
implementations: Record<string, Callable>, // Key is a signature
factories: Record<string, Factory>
}
type DispatcherInstance = {
implementationData: Record<string, ImpHolder>, // Key is an operation name
types: TypeSpec[], // Order is order to try types in
behaviors: Record<string, Callable>, // Key is opname; actually executable!
}
function newDispatcherInstance(): DispatcherInstance {
return {
implementationData: {},
types: [],
behaviors: {}
}
}
function isTypeSpec(spec: ImpSpecification | ImpSpecs): spec is TypeSpec {
if ('name' in spec
&& typeof spec.name === 'string'
&& 'test' in spec
&& 'from' in spec
) {
return true
}
return false
}
// The assemble function creates a dispatcher from a mess of specifications
export function assemble(specifications: ImpSpecs, into?: DispatcherInstance ) {
if (into === undefined) {
into = newDispatcherInstance()
const show = inspect(specifications, {depth: 18, colors: true})
console.log('Specifications are', show)
}
for (const specName in specifications) {
console.log('Processing', specName)
const spec = specifications[specName]
if ('_reflectedType5' in spec) {
if (isTypeSpec(spec)) {
registerTypeSpec(spec, into)
continue
}
// implementations that we need to deal with
console.log('Need to incorporate', Object.keys(spec))
} else {
// Just another layer of specification
assemble(spec as ImpSpecs, into)
}
}
into.behaviors.typeOf = (a: unknown) => whichType(a, into.types)
return into.behaviors
}
function registerTypeSpec(typeSpec: TypeSpec, into: DispatcherInstance) {
let position = into.types.length
if ('before' in typeSpec) {
for (const typeName of typeSpec.before) {
const typeIndex = into.types.findIndex(t => t.name = typeName)
if (typeIndex >= 0 && typeIndex < position) position = typeIndex
}
}
into.types.splice(position, 0, typeSpec)
// likely there will be more to do in the long run
}
// Returns the string name of the type of _a_ per the type specifications
// in _types_, or 'unknown' if no type matches
function whichType(a: unknown, types: TypeSpec[]) {
for (const typeSpec of types) {
const typeName = typeSpec.name
// First check if this is a ground type or a generic:
const typeSpecType = (typeSpec as TypeSpec & Reflected)._reflectedType5
if (!('_typeParameters' in typeSpecType.test)) {
// ground type, just test it
if (typeSpec.test(a)) return typeName
continue
}
// Generic type. In this case, the test will be a factory, dependent
// on a test for each of the type parameters. So assemble those
// dependencies:
const typePars = typeSpecType.test._typeParameters
const permissiveTests = Object.fromEntries(typePars.map(k =>
[`test${k}`, x => true]))
const testAllUnknown = typeSpec.test(permissiveTests)
if (!testAllUnknown(a)) continue
// Here, a seems to be in some instantiation of this generic type.
// Need to infer which instantiation
const thisInfer = typeSpec.infer({typeOf: x => whichType(x, types)})
const typeArguments = thisInfer(a)
return `${typeName}<${typePars.map(k => typeArguments[k]).join(',')}>`
}
return 'unknown'
}

View File

@ -1,5 +1,10 @@
import {inspect} from 'node:util' import {assemble} from '@/core/Dispatcher'
import * as specifications from './all' import * as specifications from './all'
console.log(inspect(specifications, {depth: 18, colors: true})) const math = assemble(specifications)
console.log('PRODUCED', math)
console.log(math.typeOf(17))
console.log(math.typeOf({re: 3.4, im: -0.1}))
console.log(math.typeOf({re: {re: 1, im: 0}, im: {re: 0, im: -1}}))
console.log(math.typeOf('no string type yet'))