展平嵌套元组类型并保留顺序

Flatten nested tuple type and preserve order

我正在尝试完成这样的事情:

type Type = object;
type TypeTuple = readonly Type[];

function flattenTuples<T extends readonly (Type | TypeTuple)[], R = Flatten<T>>(...tuples: T): R {
  // flatten tuple and return with correct ordering
  // example: flattenTuples(A, B, [C, D], [A]) => [A, B, C, D, A]
}

其中 flattenTuples 函数将展平提供的参数中的每个元组,类型 Flatten<T> 实现将做同样的事情,return 一个元组,例如。 “as const”数组并保留参数元组的顺序。我只需要 1 级展平。

再举个例子(A、B 等都是不同的 class 构造函数):

const flat = flattenTuples(A, B, [C, D], [A]);
// this would make the variable flat's type:
// [A, B, C, D, A]

我尝试了类似 question 的答案,但他的 Flatten 类型的解决方案不起作用。对于上面的示例,它生成类型 [A, B, C | D, A]

TS4.0 更新:

TS4.0 将引入 variadic tuple types,其中连接固定数量的元组 ABC 就像使用 [=16] 一样简单=].这意味着 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, a proposal to support variadic (arbitrary length) kinds (basically "types of types"). Right now the only officially-supported way to manipulate tuples in a variadic way is to prepend a fixed number of types onto the beginning, by using tuples in rest/spread positions


所以“官方”答案类似于“你不能或不应该这样做”,但这并不能阻止人们这样做。甚至还有一个名为 ts-toolbelt which does all kinds of fun recursive things under the covers to get more arbitrary tuple manipulation. I think the author of this library has really tried to make sure that the compiler performance doesn't suffer, so if I were going to really use anything, I'd probably use that library rather than write it myself. One of the tools on that belt is called Flatten<L> which seems to do what you want. But even this library is still 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]应该是7Sub[7][3]应该是4Tup[3]应该是[0,0,0].

从那里我定义了 Tail<T>,它采用像 [1,2,3,4] 这样的元组并剥离第一个元素以产生 [2,3,4],以及 Concat<T, U> 它采用两个元组和连接它们(比如 Concat<[1,2],[3,4]> 应该是 [1,2,3,4])。这里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-ed Tuplized<T>.

Tail 版本

让我们看一个例子:

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

TypeScript 已经 a proposal for variadic kinds our thats currently slated for 4.0 让 jcalz@ 的解决方案更加出色:

type Tuple = readonly any[]
type Tail<T extends Tuple> = T extends [any, ...infer U] ? U : []
type Concat<T extends Tuple, U extends Tuple> = [...T, ...U];
type Tuplize<T> = { [K in keyof T]: T[K] extends unknown[] ? T[K] : [T[K]] }

type Flatten<T extends Tuple> =
  Tuplize<T> extends infer U ?
    U extends Tuple ?
      { 0: [], 1: Concat<U[0], Flatten<Tail<U>>>}[U extends [] ? 0 : 1]
      : never
    : never;

虽然这是基于一个实验性的提议,但没有像 jcalz@ 的答案那样的递归控制,而且,出于显而易见的原因,不应在生产中使用,直到 recursive conditional typees are actually officially supported 直到 4.0 真正发布并且可变类型的语法最终确定。

虽然做梦很有趣,对吧?


Playground Link