Say I wanted to generate a union type of numbers that go up by a scale, but the amount of entries in the union will be quite large, could I generate that without explicitly typing out every type in the union?
Say I have type ScaleByEight = 8 | 16 | 24 | 32 ... 400;
Could I create a utility that where I can pass a min, max and value to scale by?
Something like type ScaleByEight = ScaleBy</* min */ 8, /* max */, 400, /* scale by */ 8>;
CodePudding user response:
There is no Range
type at the moment.
It's an open issue on GitHub.
CodePudding user response:
It is possible, however I would not rely on this logic too much on PROD code:
type MAXIMUM_ALLOWED_BOUNDARY = 999
type ComputeRange<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: ComputeRange<N, [...Result, Result['length']]>
)
type Add<A extends number, B extends number> = [...ComputeRange<A>, ...ComputeRange<B>]['length']
type IsGreater<A extends number, B extends number> = IsLiteralNumber<[...ComputeRange<B>][Last<[...ComputeRange<A>]>]> extends true ? false : true
type Last<T extends any[]> = T extends [...infer _, infer Last] ? Last extends number ? Last : never : never
type RemoveLast<T extends any[]> = T extends [...infer Rest, infer _] ? Rest : never
type IsLiteralNumber<N> = N extends number ? number extends N ? false : true : false
type AddIteration<Min extends number, Max extends number, ScaleBy extends number, Result extends Array<unknown> = [Min]> =
IsGreater<Last<Result>, Max> extends true
? RemoveLast<Result>
: AddIteration<
Min, Max, ScaleBy, [...Result, Add<Last<Result>, ScaleBy>]
>
// [5, 13, 21, 29, 37]
type Result = AddIteration<5, 40, 8>
Please see my answer and article
Explanation for ComputeRange
you can find in this answer and my article
Add
- just concatenates two arrays and returns new length. Just like :
const add=(xl:number[], yl:number[]) => [...xl,...yl].length
IsLiteralNumbe
- checks whether provided number is literal number like 2
or 42
or just a general type of number
.
Last
returns last element of an array
IsGreater
checks whether last element of ComputeRange<A>
can be used as an index for ComputeRangeB
. Please keep in mind that all elements in ComputedRange
are also indexes.
AddIteration
- is a recursive utility type, which checks whether last element of Result is greater then Max. If yes - return Result without last element otherwise, call itself recursively with addition.
Here you can test js representation. I made it as close as I could, but not everything can be expressed.
const add = (a: number, b: number) => [...computeRange(a), ...computeRange(b)]['length']
const isLiteralNumber = (value: any) => typeof value === 'number'
const last = (xl: number[]) => {
//const [...rest, last] = xl
return xl[xl.length - 1]
}
const computeRange = (n: number) => Array(n).fill(0).map((_, index) => index)
const isGreater = (a: number, b: number) => isLiteralNumber([...computeRange(b)][last(computeRange(a))]) ? false : true
const addIteration = (min: number, max: number, scaleBy: number, result = [min]): number[] => {
if (isGreater(last(result), max)) {
return result
}
return addIteration(min, max, scaleBy, [...result, add(last(result), scaleBy)])
}
const result = addIteration(5, 40, 8)
// [ 5, 13, 21, 29, 37, 45 ]
console.log({ result })