Reputation: 15492
How can I do addition in the type system in TypeScript?
I can get successor (add 1) and predecessor (subtract 1) but haven't figured out how to get the recursion needed in order to add two numbers generally:
type Z = 0 // zero
type S<T> = { item: T } // successor
type P<T extends S<any>> = T['item'] // predecessor
type N = S<any> | Z // number, not exactly right because S<boolean> isn't a number
const zero = 0
const one: S<Z> = { item: 0 }
const two: S<S<Z>> = { item: { item: 0} }
const three: S<S<S<Z>>> = { item: { item: { item: 0 } } }
const predPred3: P<P<typeof three>> = one; // OK
The problem I'm hitting is with recursive type definitions:
// Error: Type alias 'Plus' circularly references itself
type Plus<T1, T2> = T2 extends Z ? T1 : Plus<T1, P<T2>>
I tried to hack around the problem by using ReturnType
but the syntax isn't even correct:
function plus<T1 extends N, T2 extends N>(t1: T1, t2: T2) : T2 extends Z ? T1 : ReturnType<typeof plus<T1, P<T2>>> {
return t2 === 0? t1 : plus({item: t1}, (t2 as S<any>).item);
}
Is there a way to do addition in the type system?
Upvotes: 4
Views: 684
Reputation: 748
You probably still shouldn't use this in production, but I've created the @rimbu/typical
library to do this. It basically performs calculations on template literal strings and converts them from and to numbers.
You can use it like this:
import { Num } from "@rimbu/typical"
type A1 = Num.Add<993, 12>
// type of A1 is inferred to be 1005
type A2 = Num.Subtract<1005, 12>
// type of A2 is inferred to be 993
type A3 = Num.Div<1005, 12>
// type of A3 is inferred to be 83
It has many more operations and also does similar tricks for type-level string manipulation.
You can see it in action in this CodeSandBox
Upvotes: 2
Reputation: 51559
To evade "Type alias 'Plus' circularly references itself" restriction, you have to reference recursive type in the object member type, then select appropriate member type using []
as indexed type access operator:
type Plus<T1, T2> = {
z: T1;
s: T2 extends S<any> ? S<Plus<T1, P<T2>>> : never
// conditional type necessary because compiler can't figure out
// that T2 always extends S here, "never" branch is never taken
}[T2 extends Z ? 'z' : 's']
const p12: Plus<typeof one, typeof two> = three; // typechecks
However, be warned by this comment
unless someone like @ahejlsberg can tell us if we can expect things like that to keep working or not
It's clever, but it definitely pushes things well beyond their intended use. While it may work for small examples, it will scale horribly. Resolving those deeply recursive types consumes a lot of time and resources and might in the future run afoul of the recursion governors we have in the checker.
Don't do it!
Upvotes: 4