feat: Incremental building and simultaneous overloading of object values #5

Merged
glen merged 1 commits from stepfive into main 2022-09-26 05:39:44 +00:00
3 changed files with 103 additions and 2 deletions

View File

@ -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.] 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] 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] 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. 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. 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. 5. Attempt to eliminate redundant specification of implementation signatures.

26
src/steps/five.ts Normal file
View File

@ -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'))

View File

@ -10,7 +10,8 @@ export default function overload<T extends readonly [...any[]]>(
const impTypes = reflect(callSite).parameters[0].elements const impTypes = reflect(callSite).parameters[0].elements
return <any>((...a: any[]) => { return <any>((...a: any[]) => {
for (let i = 0; i < imps.length; ++i) { 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 let match = true
const haveArgs = a.length const haveArgs = a.length
let onArg = 0 let onArg = 0
@ -40,3 +41,77 @@ export default function overload<T extends readonly [...any[]]>(
+ 'did not match any implementation') + 'did not match any implementation')
}) })
} }
type AVO = Record<string, readonly [...any[]]>
type MergeArrayValues<L extends AVO, R extends AVO> =
Pick<L, Exclude<keyof L, keyof R>>
& Pick<R, Exclude<keyof R, keyof L>>
& { [P in (keyof L & keyof R)]: [...L[P], ...R[P]] }
function mergeImps<T extends AVO, U extends AVO>(
impT: T, impU: U): MergeArrayValues<T, U> {
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<keyof T & keyof U, string>
result[k] = [...impT[k], ...impU[k]]
} else {
result[key] = impT[key]
}
} else {
result[key] = impU[key]
}
}
return result as MergeArrayValues<T, U>
}
class ImpMerger<T extends AVO> {
implementations: T
constructor(imps: T) {
this.implementations = imps
}
with<U extends AVO>(
moreImps: U, callSite?: CallSite): ImpMerger<MergeArrayValues<T,U>> {
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<T,U>(this.implementations, moreImps))
}
imps(): T {
return this.implementations
}
}
function merge<T extends AVO>(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<T extends AVO> = {
[P in keyof T]: UnionToIntersection<T[P][number]>}
function overloadValues<T extends AVO>(impObject: T) : OverloadedValues<T> {
return Object.fromEntries(Object.entries(impObject).map(
([k, v]) => [k, overload(v)])) as OverloadedValues<T>
}
export {merge, mergeImps, overloadValues}