feat: New array-style overloading

Step two didn't work so well, so this is actually step three. Avoids
  too much redundant type information.
This commit is contained in:
Glen Whitney 2022-09-25 10:39:16 -04:00
parent c4bb415b5e
commit 8a2ae79c90
5 changed files with 138 additions and 4 deletions

View File

@ -4,8 +4,10 @@ Proof of concepts for a PocoMath-adjacent approach to a possible math.ts (TypeSc
Roadmap:
1. Install over.ts and get an example of add with number and bigint implementations working with it.
2. Use the builder pattern to get add working with its implmentations defined incrementally before producing the final overload.
1. Install over.ts and get an example of add with number and bigint implementations working with it. [DONE]
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. [IN PROGRESS]
3. Use the builder pattern to get a single object with both an add and a negate method, with both defined incrementally, working.
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.

12
src/steps/three.ts Normal file
View File

@ -0,0 +1,12 @@
import overload from '../util/overload.js'
const adder = overload([
(x: number, y: number) => {
if (typeof x === 'number' && typeof y === 'number') return x + y
throw new TypeError('Can only add numbers')
},
(x: string, y: string) => 'Yay' + x + y
])
console.log(adder(1, 2))
console.log(adder('a', 'b'))

102
src/steps/two.ts Normal file
View File

@ -0,0 +1,102 @@
import { useTypes } from 'over.ts/src/index.js';
const types = {
number: (x: unknown): x is number => typeof x === 'number',
bigint: (x: unknown): x is bigint => typeof x === 'bigint'
}
const overload = useTypes(types)
type TypeName = keyof typeof types
//type Tuple<T> = T | `${T}, ${Tuple<T>}`
//type TypeTuple = Tuple<TypeName>
type CheckTuple<T> = T extends TypeName
? T
: T extends `${TypeName}, ${infer Rest}`
? CheckTuple<Rest> extends string ? T : false
: false
type CheckDecl<T> = T extends `${infer Args} -> ${TypeName}`
? CheckTuple<Args> extends string ? T : never
: never
function check<T extends string>(d: CheckDecl<T>) {
console.log(d)
}
check('number, bigint -> bigint')
check('bigint -> number')
// check('string -> number') // would be error as desired
class ImpBuilder {
addImp<T extends string, U>(decl: CheckDecl<T>, imp: U) {
return Object.assign(this, {[decl]: imp} as Record<T, U>)
}
// finalize<T extends Record<string, unknown>>(this: T) {
// return overload(this as Omit<T, 'addImp' | 'finalize'>)
// }
finalize() {
delete this.addImp
delete this.finalize
return this
}
}
function fixImps<T extends {addImp: any}>(imps: T): Omit<T, 'addImp'> {
const res = Object.assign({}, imps)
delete res.addImp
return res
}
function overloadImps<T>(imps: T) {
return overload(imps)
}
const negateImps = new ImpBuilder()
.addImp('number -> number', (a: number) => -a)
.addImp('bigint -> bigint', (a: bigint) => -a)
const negate = overload(negateImps.finalize() as Omit<typeof negateImps, 'finalize'|'addImp'>)
console.log('Negation of 5 is', negate(5))
console.log('Negation of 5n is', negate(5n))
const addImps = new ImpBuilder() // imagining this is in some "initialize a proto-bundle" file
addImps.addImp('number, number -> number', (a: number, b: number) => a+b) // imagining this line in the `number` file
const addAll = addImps.addImp('bigint, bigint -> bigint', (a: bigint, b: bigint) => a+b) // and this is in the `bigint` file
const add = overload(addAll.finalize() as Omit<typeof addAll, 'finalize'|'addImp'>) // We wrap everything up elsewhere
console.log('Sum of 5 and 7 is', add(5,7))
console.log('Sum of 5n and 7n is', add(5n, 7n))
try {
//@ts-expect-error
console.log('Mixed sum is', add(5n, 7))
} catch {
console.log('Mixed sum errored as expected.')
}
/*****
const addImps = new ImpBuilder()
//addImps.addImp('number, number -> number', (a: number, b: number) => a+b)
const subAddImps = addImps.addImp('number, number -> number', (a: number, b: number) => a+b).addImp('bigint, bigint -> bigint', (a: bigint, b: bigint) => {
console.log('adding bigints')
return a+b
})
//const finalAddImps = addImps.finalize()
const add = overload(subAddImps.finalize() as Omit<typeof subAddImps, 'finalize'|'addImp'>)
console.log('Sum of 5 and 7 is', add(5,7))
console.log('Sum of 5n and 7n is', add(5n, 7n))
try {
//@ts-expect-error
console.log('Mixed sum is', add(5n, 7))
} catch {
console.log('Mixed sum errored as expected.')
}
****/

17
src/util/overload.ts Normal file
View File

@ -0,0 +1,17 @@
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
? I
: never;
export default function overload<T extends readonly [...any[]]>(
imps: T): UnionToIntersection<T[number]> {
return <any>((...a: any[]) => {
for (let i = 0; i < imps.length; ++i) {
try {
const val = imps[i](...a)
return val
} catch {
}
}
})
}

View File

@ -5,7 +5,8 @@
"rootDir": "src",
"outDir": "obj"
},
"files": [
"src/steps/one.ts"
"include": [
"src/steps/three.ts",
"src/util/*.ts"
]
}