Issue
Suppose I had the following types
type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type Three = 3;
Is there some kind of type Add<T, U>
I could define that adds numbers on the type level like so?
type SmallOdds = Add<One, SmallEvens>; // same as `1 | 3 | 5 | 7 | 9 | 11`
type Four = Add<One, Three> // same as `4`
Alternatively, should I be looking at alternative representations of numbers to achieve this effect? However, being able to convert to something that extends number
so that I can use it for tuple indexing would be a big plus.
Solution
After some light searching I found this fascinating article - Implementing Arithmetic Within TypeScript’s Type System - which does demonstrate a way to achieve what you're attempting using some cutting edge features.
We can create a type which adds two numbers together as follows:
type Length<T extends any[]> =
T extends { length: infer L } ? L : never;
type BuildTuple<L extends number, T extends any[] = []> =
T extends { length: L } ? T : BuildTuple<L, [...T, any]>;
type Add<A extends number, B extends number> =
Length<[...BuildTuple<A>, ...BuildTuple<B>]>;
type Seven = Add<3, 4> // 7
I'd recommend reading the article for the author's explanation (and many more arithmetic types). However, in a nutshell:
The type Length
infers the length property of an array type which is passed to it. Where that array type is a tuple, it will provide the actual length rather than just number
.
Length<[number, number, string]> // 3
The BuildTuple
type will create a tuple type of length L
populated with the type any
. This works by using the spread operator to recursively increment the length of the tuple until it matches the conditional for length: L
, at which point it will be returned.
BuildTuple<3> // [any, any, any]
The Add
type should now be fairly self-explanatory. We build out tuples to the two lengths of the two numbers provided, then spread them to a new tuple and get the combined length.
While the author does not include adding onto union types, we can do that fairly easily ourselves with a mapped type:
type MappedAdd<A extends number, B extends number> = {
[key in B]: Add<A, key>
}[B]
type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type SmallOdds = MappedAdd<One, SmallEvens>; // 1 | 3 | 5 | 7 | 9 | 11
For this use case it works great. However, eventually the recursivenss of the tuple creating type will become too deep, limiting this to numbers under 45 (or 44 for the mapped version):
Add<1, 45> // Type instantiation is excessively deep and possibly infinite.
Answered By - lawrence-witt
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.