TS4.0 更新:
TS4.0 将引入variadic tuple types,其中连接固定数量的元组A、B、C,就像使用[...A, ...B, ...C] 一样简单。这意味着Flatten<T> 可以像这样实现something:
type ConcatX<T extends readonly (readonly any[])[]> = [
...T[0], ...T[1], ...T[2], ...T[3], ...T[4],
...T[5], ...T[6], ...T[7], ...T[8], ...T[9],
...T[10], ...T[11], ...T[12], ...T[13], ...T[14],
...T[15], ...T[16], ...T[17], ...T[18], ...T[19]
];
type Flatten<T extends readonly any[]> =
ConcatX<[...{ [K in keyof T]: T[K] extends any[] ? T[K] : [T[K]] }, ...[][]]>
type InputTuple = [A, B, [C, D], [A, B, D], [A, B, C, D, B], A];
type FlattenedTuple = Flatten<InputTuple>;
// type FlattenedTuple = [A, B, C, D, A, B, D, A, B, C, D, B, A]
它仍然不适用于任意长的元组(或开放式元组等边缘情况),但它不像以前那么疯狂了。
Playground link
PRE-TS4.0 答案:
TypeScript 类型系统并不真正适合让您这样做。 Flatten 之类的最明显的实现是递归条件类型。这是not currently supported (see microsoft/TypeScript#26980)。你可以这样做,但不能保证它会在未来的 TypeScript 版本中继续工作。即使你得到了一个工作版本,它也很容易让它破坏 TypeScript 编译器,导致编译时间异常长,甚至编译器挂起和崩溃。我作为测试编写的Flatten 的第一个版本存在这个问题,即使是长度为 7 的输出元组也需要很长时间才能工作,并且偶尔会报告类型实例化深度错误。
我认为此功能的规范 GitHub 问题可能是 microsoft/TypeScript#5453,支持 variadic (arbitrary length) kinds (basically "types of types") 的提议。目前唯一官方支持的以可变方式操作元组的方法是在开头添加固定数量的类型,使用tuples in rest/spread positions。
因此,“官方”的回答类似于“您不能或不应该这样做”,但这并不能阻止人们这样做。甚至还有一个名为 ts-toolbelt 的库,它在后台执行各种有趣的递归操作,以获得更任意的元组操作。我认为这个库的作者确实试图确保编译器性能不会受到影响,所以如果我真的要使用任何东西,我可能会使用那个库而不是自己编写它。那个腰带上的一个工具叫做Flatten<L>,它似乎可以做你想做的事。但即使这个库仍然是not officially supported。
我还是忍不住写了我自己的Flatten 版本,让你知道它有多毛茸茸。它似乎表现得足够好。我将其限制为最多仅连接大约 7 个元组,并且展平输出的总长度不能超过大约 30 个元素。它使用迭代和递归条件类型,后者不受支持。一个特别聪明的人可能会想出一个完全迭代的方法,但我要么不是那个人,要么我需要很长时间才能成为那个人。好的,前言已经足够了,这里是:
/*
codegen
var N = 30;
var range = n => (new Array(n)).fill(0).map((_,i)=>i);
var s = [];
s.push("type Add = ["+range(N).map(i => "["+range(N-i).map(j => i+j+"").join(",")+"]").join(",")+"];")
s.push("type Sub = ["+range(N).map(i => "["+range(i+1).map(j => i-j+"").join(",")+"]").join(",")+"];")
s.push("type Tup = ["+range(N).map(i => "["+range(i).map(_=>"0").join(",")+"]").join(",")+"];")
console.log(s.join("\n"))
*/
type Add = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], [21, 22, 23, 24, 25, 26, 27, 28, 29], [22, 23, 24, 25, 26, 27, 28, 29], [23, 24, 25, 26, 27, 28, 29], [24, 25, 26, 27, 28, 29], [25, 26, 27, 28, 29], [26, 27, 28, 29], [27, 28, 29], [28, 29], [29]];
type Sub = [[0], [1, 0], [2, 1, 0], [3, 2, 1, 0], [4, 3, 2, 1, 0], [5, 4, 3, 2, 1, 0], [6, 5, 4, 3, 2, 1, 0], [7, 6, 5, 4, 3, 2, 1, 0], [8, 7, 6, 5, 4, 3, 2, 1, 0], [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]];
type Tup = [[], [0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
type Arr = readonly any[];
type Tail<T extends Arr> =
((...x: T) => void) extends ((h: infer A, ...t: infer R) => void) ? R : never
type Concat<T extends Arr, U extends Arr> = Tup[Add[T["length"]][U["length"]]] extends infer A ? {
[I in keyof A]: I extends keyof T ? T[I] : U[Sub[Extract<I, keyof Sub>][T["length"]]]
} : never
// in TS4.0, Tail and Concat can be simplified to
// type Tail<T extends Arr> = T extends [infer A, ...infer R] ? R : never;
// type Concat<T extends Arr, U extends Arr> = [...T, ...U];
type Tuplize<T> = { [K in keyof T]: T[K] extends any[] ? T[K] : [T[K]] }
type Flatten<T extends readonly any[], N extends number = 7> =
N extends 0 ? [] :
Tuplize<T> extends infer U ? U extends Arr ?
{ 0: [], 1: Concat<U[0], Extract<Flatten<Tail<U>, Extract<Sub[N][1], number>>, Arr>> }[
U extends [] ? 0 : 1] : never : never;
前三行是由一个小的 JS 脚本生成的,生成一堆固定的元组,表示加减数字的操作,同时也得到一个给定长度的“空白”元组。所以Add[3][4]应该是7,Sub[7][3]应该是4,Tup[3]应该是[0,0,0]。
从那里我定义Tail<T>,它接受一个像[1,2,3,4] 这样的元组并剥离第一个元素以产生[2,3,4],以及Concat<T, U>,它接受两个元组并将它们连接起来(比如Concat<[1,2],[3,4]>应该是@ 987654356@)。这里Concat的定义是纯迭代的,所以还不违法。
然后我创建Tuplize<T>,它只是确保元组T 的每个元素本身都是一个数组。所以你的[A, B, [C, D], [A]] 会变成[[A],[B],[C,D],[A]]。这消除了扁平化时出现的奇怪边缘条件。
最后我写了非法和递归的Flatten<T>。我试图在那里设置一些递归限制;它只适用于最长 7 左右的长度。如果你试图通过将7 更改为25 来增加它,你很可能会从编译器中得到错误。无论如何,这里的基本方法是对Tuplize<T> 执行一种reduce() 操作:只需将Concat 的第一个元素Tuplize<T> 放到Flatten 的Tail 的Tail 的版本上Tuplized<T> .
我们来看一个例子:
type InputTuple = [A, B, [C, D], [A, B, D], [A, B, C, D, B], A];
type FlattenedTuple = Flatten<InputTuple>;
// type FlattenedTuple = [A, B, C, D, A, B, D, A, B, C, D, B, A]
看起来不错。
这里有各种各样的警告;它只会变平一层(这就是你所要求的)。它可能不会分配给工会。对于 readonly 或可选的元组或数组,它可能不会按照您想要的方式运行。对于太长或任意长度的元组,它肯定不能正常工作。如果满月或木星与火星对齐等,它可能无法正常运行。
上述代码的重点是不是让您将其放入您的生产系统中。请不要那样做。只是想在类型系统上找点乐子,并表明这是一个未解决的问题。
好的,希望对您有所帮助;祝你好运!
Playground link to code