简体   繁体   English

如何在 TypeScript 中通过文字联合实现类型级最大值 function?

[英]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

Playground 操场

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!祝你好运!

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM