【问题标题】:Flatten nested tuple type and preserve order展平嵌套元组类型并保留顺序
【发布时间】:2023-03-03 09:11:17
【问题描述】:

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

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&lt;T&gt; 实现将执行相同的操作并返回一个元组,例如。 "as const" 数组并保留参数元组的顺序。我只需要 1 级展平。

再举例(A、B等都是不同的类构造器):

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]

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    TS4.0 更新:

    TS4.0 将引入variadic tuple types,其中连接固定数量的元组ABC,就像使用[...A, ...B, ...C] 一样简单。这意味着Flatten&lt;T&gt; 可以像这样实现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&lt;L&gt;,它似乎可以做你想做的事。但即使这个库仍然是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&lt;T&gt;,它接受一个像[1,2,3,4] 这样的元组并剥离第一个元素以产生[2,3,4],以及Concat&lt;T, U&gt;,它接受两个元组并将它们连接起来(比如Concat&lt;[1,2],[3,4]&gt;应该是@ 987654356@)。这里Concat的定义是纯迭代的,所以还不违法。

    然后我创建Tuplize&lt;T&gt;,它只是确保元组T 的每个元素本身都是一个数组。所以你的[A, B, [C, D], [A]] 会变成[[A],[B],[C,D],[A]]。这消除了扁平化时出现的奇怪边缘条件。

    最后我写了非法和递归的Flatten&lt;T&gt;。我试图在那里设置一些递归限制;它只适用于最长 7 左右的长度。如果你试图通过将7 更改为25 来增加它,你很可能会从编译器中得到错误。无论如何,这里的基本方法是对Tuplize&lt;T&gt; 执行一种reduce() 操作:只需将Concat 的第一个元素Tuplize&lt;T&gt; 放到FlattenTailTail 的版本上Tuplized&lt;T&gt; .

    我们来看一个例子:

    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

    【讨论】:

    • 感谢您的深入回答和 github 问题的链接!正是我需要的,太糟糕了,没有一种不那么老套的方法。
    • Looks like variadic types are slated for TS 4.0 正如你提到的那样,这可能有助于更清洁
    • 继续为像我这样想知道在可变参数类型和对递归条件类型的官方支持的世界中您的出色解决方案会是什么样子的人做出回答。
    【解决方案2】:

    TypeScript 提供了 a proposal for variadic kinds 我们目前的 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-05
      • 2014-03-29
      • 2017-10-10
      • 2020-10-04
      • 1970-01-01
      • 1970-01-01
      • 2012-11-21
      • 2019-11-03
      相关资源
      最近更新 更多