feat: Incremental building and simultaneous overloading of object values #5
@ -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.
|
||||
|
26
src/steps/five.ts
Normal file
26
src/steps/five.ts
Normal 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'))
|
@ -10,7 +10,8 @@ export default function overload<T extends readonly [...any[]]>(
|
||||
const impTypes = reflect(callSite).parameters[0].elements
|
||||
return <any>((...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<T extends readonly [...any[]]>(
|
||||
+ '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}
|
||||
|
Loading…
Reference in New Issue
Block a user