[英]How to implement a type-level max function over a union of literals, in TypeScript?
Given a union of number literals, eg:给定数字文字的联合,例如:
type Values = 1 | 2 | 5
Is it possible to create a generic type-level function that extracts the maximum, ie:是否可以创建一个提取最大值的通用类型级function,即:
type Max<T> = ???
type V = Max<Values>
// V = 5
Oh boy.好家伙。 I created a monster that seems to work, but because of the limitations of circular types, it cannot be done as a single step.
我创造了一个怪物,似乎可以工作,但由于循环类型的限制,它不能一步完成。
type DropFirst<T extends TupleLike> = T extends readonly [any, ...infer U] ? U : [...T];
type Longer<T, U extends TupleLike> = U extends [] ? T : T extends [any, ...U] ? T : U;
type TupleLike<T = unknown> = readonly T[] | readonly [T];
type Head<T> = T extends [infer U, ...any[]] ? U : never;
type TupleOf<T, Length extends number, Queue extends any[] = []> = {
done: Queue,
next: TupleOf<T, Length, Prepend<T, Queue>>;
}[Yield<Queue, Length>]
type Yield<Queue extends any[], Length extends number> =
Queue['length'] extends Length
? "done"
: "next"
type Prepend<T, U extends any[]> =
((head: T, ...tail: U) => void) extends ((...all: infer V) => void)
? V
: []
type FindLongestTuple<T extends readonly number[][], Accumulator extends number[] = []> = {
done: Accumulator;
next: FindLongestTuple<DropFirst<T>, Longer<Head<T>, Accumulator>>;
}[T extends [] ? 'done' : 'next'];
type GenerateTuples<T extends readonly number[]> = {
[K in keyof T]: TupleOf<1, T[K] & number>
}
/**
* Usage:
*/
type Input = [4, 5, 6, 1, 2];
type Step1 = GenerateTuples<Input>;
type Step2 = FindLongestTuple<Step1>
type Result = Step2['length']; // 6
I bet there is a simpler way to do that.我敢打赌,有一种更简单的方法可以做到这一点。 ;-)
;-)
The tersest illegal (see microsoft/TypeScript#26980 ) version of Max
I can come up with (that uses variadic tuples slated to come out in TS4.0) is this:我能想出的
Max
最简单的非法(参见microsoft/TypeScript#26980 )版本(使用计划在 TS4.0 中出现的可变参数元组)是:
type MaxIllegal<N extends number, T extends any[] = []> = {
b: T['length'], r: MaxIllegal<N, [0, ...T]>
}[[N] extends [Partial<T>['length']] ? "b" : "r"];
type MaxValuesIllegal = MaxIllegal<Values>; // 5
That sort of illegal circularity works until it doesn't, which is one reason it's not supported.这种非法循环一直有效,直到它不起作用,这是它不被支持的原因之一。 If you put in a value other than a non-negative integer, it recurses until there's a compiler error:
如果您输入非负 integer 以外的值,它将递归直到出现编译器错误:
type OopsNegative = MaxIllegal<-1 | 3>; // error! Type instantiation is excessively deep and possibly infinite.
This can probably be worked around, but it gets worse: the recursion here bottoms out after only a few dozen levels, so you can't even use a number like 55
without getting an error:这可能可以解决,但情况会变得更糟:这里的递归仅在几十级后触底,所以你甚至不能使用像
55
这样的数字而不会出错:
type OopsTooBig = MaxIllegal<3 | 55 | 9>; // error! Type instantiation is excessively deep and possibly infinite.
One alternative is to programmatically generate a helper type like LEQ
consisting of all the non-negative integers less than or equal to a given key, up to some max value you can choose... here I'm doing 100 or 99 or something:一种替代方法是以编程方式生成一个辅助类型,如
LEQ
,由小于或等于给定键的所有非负整数组成,直到您可以选择的某个最大值......这里我正在做 100 或 99 或其他东西:
// console.log("type LEQ={"+Array.from({length:100},(_,i)=>i+":"+Array.from({length: i+1},(_,j)=>j).join("|")).join(", ")+"}")
type LEQ = { 0: 0, 1: 0 | 1, 2: 0 | 1 | 2, 3: 0 | 1 | 2 | 3, 4: 0 | 1 | 2 | 3 | 4, 5: 0 | 1 | 2 | 3 | 4 | 5, 6: 0 | 1 | 2 | 3 | 4 | 5 | 6, 7: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7, 8: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8, 9: 0 | 1 | 2 | 3 | 4 | 5 ... SNIP!
Then you can write some code to pluck out the right values without using illegal circularity:然后你可以编写一些代码来提取正确的值而不使用非法循环:
type Same<T, U, Y = T, N = never> = [T] extends [U] ? [U] extends [T] ? Y : N : N;
type ToLEQ<N extends number> = { [K in keyof LEQ]: [N] extends [Exclude<LEQ[K], K>] ? never : K }[keyof LEQ]
type Max<N extends number, U extends number = ToLEQ<N>> = { [K in keyof LEQ]: Same<U, LEQ[K], K> }[keyof LEQ]
It works for your example:它适用于您的示例:
type MaxValues = Max<Values>; // 5
This works for 55
now because we're not overloading the stack:现在这适用于
55
,因为我们没有重载堆栈:
type NotTooBigAnymore = Max<3 | 55 | 9> // 55
It still doesn't work when you give it an unexpected value like -1
, but here it at least just gives you the max value it has instead of going completely off the deep end:当你给它一个像
-1
这样的意外值时它仍然不起作用,但在这里它至少只是给你它所拥有的最大值,而不是完全脱离深层:
type OopsNegativeOkayIGuess = Max<-1 | 3> // 99
and as I said you can probably work around it by filtering the passed-in value before running Max
on it.正如我所说,您可以通过在运行
Max
之前过滤传入的值来解决它。 After all we have a big LEQ
type sitting around:毕竟我们有一个大的
LEQ
类型:
type Max<N extends number, U extends number = ToLEQ<Extract<N, keyof LEQ>>> = { [K in keyof LEQ]: Same<U, LEQ[K], K> }[keyof LEQ]
And that gives you这给了你
type OopsNegativeOkayIGuess = Max<-1 | 3> // 3
which happens to be true (although Max<1 | 2.5>
will be 1
which is false).这恰好是真的(尽管
Max<1 | 2.5>
将是1
这是假的)。
In any case, this is really pushing the compiler past anything I'd be comfortable recommending for a production code base.无论如何,这确实使编译器超越了我愿意为生产代码库推荐的任何内容。 Really we should be pushing for microsoft/TypeScript#26382 to be implemented so that the compiler can just do math at the type level.
真的,我们应该推动实现microsoft/TypeScript#26382 ,以便编译器可以在类型级别进行数学运算。 So you might want to go there and give it a and possibly describe your (hopefully compelling) use case.
所以你可能想在那里 go 并给它一个并可能描述你的(希望有说服力的)用例。
Okay, hope that helps;好的,希望有帮助; good luck!
祝你好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.