From a848e2af88d714ddbdac66d4fd1f38fe9203d9ae Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 26 Sep 2022 01:36:25 -0400 Subject: [PATCH] feat: Incremental building and simultaneous overloading of object values So we can start with literal objects whose keys are operator names, and whose values are arrays of implementations, and merge them, and then replace every value with the overloaded function it specifies. --- README.md | 2 +- src/steps/five.ts | 26 +++++++++++++++ src/util/overload.ts | 77 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/steps/five.ts diff --git a/README.md b/README.md index 1630517..a8d0201 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Roadmap: 2. Use the builder pattern to get add working with its implmentations defined incrementally before producing the final overload. [Didn't quite work the way we wnated, but maybe we can do an alternative later.] 3. Make a version of over.ts, call it util/overload.ts, that takes an array of implementations without redundant type annotation. [DONE] 4. Improve that version of overload with rtti to select the implementation without the implementations having to throw errors. [DONE] -3. Use the builder pattern to get a single object with both an add and a negate method, with both defined incrementally, working. +5. Use the builder pattern to get a single object with both an add and a negate method, with both defined incrementally, working. [DONE] 4. Incorporate a subtract method that works on numbers and bigint by separate definitions but with dependencies on add and negate. 5. Incorporate a subtract method that works with one generic implementation that works for both number and bigint with dependencies on add and negate. 5. Attempt to eliminate redundant specification of implementation signatures. diff --git a/src/steps/five.ts b/src/steps/five.ts new file mode 100644 index 0000000..06254b5 --- /dev/null +++ b/src/steps/five.ts @@ -0,0 +1,26 @@ +import 'reflect-metadata' +import {reflect} from 'typescript-rtti' +import {merge, overloadValues} from '../util/overload.js' + +const numImps = { + add: [(x: number, y: number) => x + y], + negate: [(x: number) => -x] +} as const + +const strImps = { + add: [(x: string, y: string) => x + ', ' + y], + negate: [(x: string) => 'NOT ' + x] +} as const + +const merger = merge(numImps) +const mathImps = merger.with(strImps).imps() + +const math = overloadValues(mathImps) + +console.log(math.add(1.5, 2.5)) +console.log(math.add('One and a half', 'Two and a half')) +console.log(math.negate(3.5)) +console.log(math.negate('Three and a half')) + +//@ts-expect-error +console.log(math.add(1.5, 'Two and a half')) diff --git a/src/util/overload.ts b/src/util/overload.ts index f59b58b..4ca62bb 100644 --- a/src/util/overload.ts +++ b/src/util/overload.ts @@ -10,7 +10,8 @@ export default function overload( const impTypes = reflect(callSite).parameters[0].elements return ((...a: any[]) => { for (let i = 0; i < imps.length; ++i) { - const paramTypes = impTypes[i].type.parameters + const paramTypes = imps[i].functionType.parameters + || impTypes[i].type.parameters let match = true const haveArgs = a.length let onArg = 0 @@ -40,3 +41,77 @@ export default function overload( + 'did not match any implementation') }) } + +type AVO = Record + +type MergeArrayValues = + Pick> + & Pick> + & { [P in (keyof L & keyof R)]: [...L[P], ...R[P]] } + +function mergeImps( + impT: T, impU: U): MergeArrayValues { + const dummy = Object.assign({}, impT, impU) + const result = {} as AVO + for (const key in dummy) { + if (key in impT) { + if (key in impU) { + const k = key as Extract + result[k] = [...impT[k], ...impU[k]] + } else { + result[key] = impT[key] + } + } else { + result[key] = impU[key] + } + } + return result as MergeArrayValues +} + +class ImpMerger { + implementations: T + constructor(imps: T) { + this.implementations = imps + } + with( + moreImps: U, callSite?: CallSite): ImpMerger> { + const Utype = reflect(callSite).parameters[0] + // Annotate the implementations with their RTTI while we have it + for (const [key, imps] of Object.entries(moreImps)) { + if (!(imps[0].functionType)) { + const tuple = Utype.members.find(m => m.name === key) + for (let i = 0; i < imps.length; ++i) { + imps[i].functionType = tuple.type.elements[i].type + } + } + } + return new ImpMerger(mergeImps(this.implementations, moreImps)) + } + imps(): T { + return this.implementations + } +} + +function merge(impsObject: T, callSite?: CallSite) { + const Ttype = reflect(callSite).parameters[0] + // Annotate the implementations with their RTTI while we have it + for (const [key, imps] of Object.entries(impsObject)) { + if (!(imps[0].functionType)) { + const tuple = Ttype.members.find(m => m.name === key) + for (let i = 0; i < imps.length; ++i) { + imps[i].functionType = tuple.type.elements[i].type + } + } + } + return new ImpMerger(impsObject) +} + +type OverloadedValues = { + [P in keyof T]: UnionToIntersection} + +function overloadValues(impObject: T) : OverloadedValues { + return Object.fromEntries(Object.entries(impObject).map( + ([k, v]) => [k, overload(v)])) as OverloadedValues +} + +export {merge, mergeImps, overloadValues} -- 2.34.1