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.]
|
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
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
|
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}
|
||||||
|
Loading…
Reference in New Issue
Block a user