代码之家  ›  专栏  ›  技术社区  ›  Nurbol Alpysbayev

Typescript:如何在这种类型中实现递归?

  •  3
  • Nurbol Alpysbayev  · 技术社区  · 6 年前

    我最初的问题是 this

    我制作了以下类型,但由于循环引用错误,它无法工作,我不知道如何解决它:

    type Increment<T extends number, Tuple extends any[] = [any]> = 
    T extends 0 ? 1 :
    T extends 1 ? 2 :
    T extends TupleUnshift<any, Tuple> ? 
    TupleUnshift<any, TupleUnshift<any, Tuple>>['length'] :
    Increment<T, TupleUnshift<any, TupleUnshift<any, Tuple>>>
    

    最后,它的工作原理应该是:

    type five = 5
    type six = Increment<five> // 6
    

    附笔。 TupleUnshift 来自 here .

    1 回复  |  直到 6 年前
        1
  •  6
  •   jcalz    4 年前

    TS4.1的更新+

    欢迎回来!引入TypeScript 4.1 recursive conditional types ,以及 variadic tuple types 使通过递归执行“将一添加到非负整数”成为可能。它实际上表现得很好,但是 我仍然不建议在生产环境中使用它 原因我很快就会谈到。


    首先,我会的 无耻地抄袭 借鉴 this comment 通过 @lazytype 在microsoft/TypeScript#26223中,它生成任意非负整数长度的元组,而不会在深度23处打破递归限制。它通过将整数分解为两个不同幂的和(即,使用其二进制表示)并连接这些长度的元组来实现这一点。可变元组类型使元组的长度加倍变得非常容易( [...T, ...T]

    type BuildPowersOf2LengthArrays<N extends number, R extends never[][]> =
      R[0][N] extends never ? R : BuildPowersOf2LengthArrays<N, [[...R[0], ...R[0]], ...R]>;
    
    type ConcatLargestUntilDone<N extends number, R extends never[][], B extends never[]> =
      B["length"] extends N ? B : [...R[0], ...B][N] extends never
      ? ConcatLargestUntilDone<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, B>
      : ConcatLargestUntilDone<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, [...R[0], ...B]>;
    
    type Replace<R extends any[], T> = { [K in keyof R]: T }
    
    type TupleOf<T, N extends number> = number extends N ? T[] : {
      [K in N]:
      BuildPowersOf2LengthArrays<K, [[never]]> extends infer U ? U extends never[][]
      ? Replace<ConcatLargestUntilDone<K, U, []>, T> : never : never;
    }[N]
    

    然后 Increment 您想要的类型如下所示:

    type Increment<N extends number> = [0, ...TupleOf<0, N>]['length'];
    

    这将创建一个长度为的元组 N 用零填充(不管你在那里使用什么),在一个 0 然后得到它的长度。

    让我们看看它的实际行动:

    type Seven = Increment<6>; // 7
    type Sixteen = Increment<15>; // 16
    type OneHundredOne = Increment<100>; // 101
    type OneThousand = Increment<999>;  // 1000
    type SixOrElevent = Increment<5 | 10>; // 6 | 11
    type Numbah = Increment<number>; // number
    

    美好的我想这正是我们想要的。现在谈谈不好的部分以及我不推荐它的原因:

    // Don't do this
    type Kablooey = Increment<3.14> // Loading... 💥🔥💻🔥💥
    

    这将导致编译器心脏病发作;递归元组生成器永远达不到它的目标,因为不管它生成的元组有多大,索引中都没有元素 3.14


    TupleOf 如果数字的字符串表示形式中有小数点,请键入以跳出(呼出到 template literal types !):

    type TupleOf<T, N extends number> = number extends N ? T[] :
      `${N}` extends `${infer X}.${infer Y}` ? T[] : {
        [K in N]:
        BuildPowersOf2LengthArrays<K, [[never]]> extends infer U ? U extends never[][]
        ? Replace<ConcatLargestUntilDone<K, U, []>, T> : never : never;
      }[N]
    

    现在 3.14 导致 number ,但事实并非如此 4.14 ,但至少是合理的,且不过分:

    type AlsoNumber = Increment<3.14> // number
    

    不过,我仍然无法告诉任何人“请在您的生产环境中使用它”。它太脆弱了。看看上面的拜占庭类型munging,您有多确定没有一些简单的边缘情况会使编译器崩溃?您是否想负责修补这些东西,只是为了让编译器将一个添加到一个数字中?

    相反,我仍然建议进行简单的查找,直到和除非TypeScript按照中的要求对数字文本执行算术 microsoft/TypeScript#15645 和/或 microsoft/TypeScript#26382 .

    Playground link to code



    早期TS版本的先前答案:

    我认为在另一个问题和评论中,我说你可以 尝试

    interface Reduction<Base, In> {
      0: Base
      1: In
    }
    type Reduce<T, R extends Reduction<any, any>, Base =[]> =
      R[[T] extends [Base] ? 0 : 1]
    
    type Cons<H, T extends any[]> = ((h: H, ...t: T) => void) extends
      ((...c: infer C) => void) ? C : never;
    
    interface TupleOfIncrementedLength<N extends number, Tuple extends any[]=[]>
      extends Reduction<Tuple, Reduce<Tuple['length'],
        TupleOfIncrementedLength<N, Cons<any, Tuple>>, N
      >> { }
    
    type Increment<N extends number> = TupleOfIncrementedLength<N>[1] extends
      { length: infer M } ? M : never;
    

    编辑:您要求解释,因此,这里是:

    首先,定义 Cons<H, T> 使用 tuples in rest/spread positions conditional types 头型 H 和一个元组尾类型 T 并返回一个新的元组,其中头部已前置到尾部。(姓名 "cons" 源于Lisp中的列表操作和后来的Haskell。)所以 Cons<"a", ["b","c"]> 评估为 ["a","b","c"]

    作为背景,TypeScript通常试图阻止您执行循环类型。你可以使用一些工具在后门溜达 条件类型 要推迟执行,请执行以下操作:

    type RecursiveThing<T> = 
      { 0: BaseCase, 1: RecursiveThing<...T...> }[Test<...T...> extends BaseCaseTest ? 0 : 1]
    

    这应该评估为 BaseCase RecursiveThing<...T...> 非常糟糕 导致编译器在无限类型上陷入谷底的副作用,并且在实践中开始使用这些类型时经常陷入困境。例如,您可以定义 TupleOfIncrementedLength<> 这样地:

    type TupleOfIncrementedLength<N extends number, Tuple extends any[]=[]> = {
      0: Cons<any, Tuple>,
      1: TupleOfIncrementedLength<N, Cons<any, Tuple>>
    }[Tuple['length'] extends N ? 0 : 1]
    

    而且它也能起作用,即使对你来说也是如此 N 最多40或50左右。但是,如果您将它放在库中并开始使用它,您可能会发现编译时间变得非常长,甚至导致编译器崩溃。这是不一致的,所以我不能很容易地在这里生成一个例子,但它咬我足够让我避免它。这个问题有可能最终得到解决。但现在,我会遵循 advice 属于 @ahejlsberg (TypeScript的首席架构师):

    它很聪明,但它肯定会使事物远远超出其预期用途。虽然它可能适用于小的例子,但它的规模会非常大。解决这些深度递归类型会消耗大量的时间和资源,并且将来可能会与我们在检查器中的递归调控器发生冲突。

    进来 @strax discovered 那以后 interface 声明的求值方式与 type 声明是(自 类型 如果可以扩展 界面 以正确的方式,结合上面的条件类型技巧,编译器不应该陷入困境。我的实验证实了这一点,但我仍然不相信。。。可能会有一个反例,只有当你尝试编写这些东西时才会出现。

    无论如何,上面 TupleOfIncrementedLength Reduction Reduce 工作原理与以前的“中堂”版本基本相同,只是它是一个 . 我真的不能不写十页就把它拆开,我也不想写,对不起。实际输出为a 减少 1 属性具有我们关心的元组。

    之后, 定期的加薪 定义为 增加长度的元组 1. 属性及其提取 length TupleOfIncrementedLength<N>[1] 是数组类型。幸运的是,条件类型推断节省了我们的时间)。


    只要说它不断增加元组的长度,直到它的长度比 参数,然后返回该元组的长度。对于小公司来说,它确实有效 N

    type Seven = Increment<6>; // 7
    type Sixteen = Increment<15>; // 16
    

    但是,至少在我的编译器(版本3.3.0-dev.20190117)上,会发生以下情况:

    type TwentyThree = Increment<22>; // 23
    type TwentyWhaaaa = Increment<23>; // {} 😞
    type Whaaaaaaaaaa = Increment<100>; // {} 🙁
    

    而且,你会对工会和工会感到有些奇怪 数字

    type WhatDoYouThink = Increment<5 | 10>; // 6 😕
    type AndThis = Increment<number>; // 1 😕
    

    您可能可以通过以下方法修复这两种行为: conditional types ,但这感觉就像你在救一艘正在下沉的船。

    the answer to the other question . 对于那些在家里随波逐流、希望在一个地方看到答案的人来说,这是:

    type Increment<N extends number> = [
      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,30,31,32,33,34,35,36,37,
      38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54, // as far as you need
      ...number[] // bail out with number
    ][N]
    

    这相对简单,适用于更高的数字:

    type Seven = Increment<6>; // 7
    type Sixteen = Increment<15>; // 16
    type TwentyThree = Increment<22>; // 23
    type TwentyFour = Increment<23>; // 24
    type FiftyFour = Increment<53>; // 54
    

    type NotFiftyFiveButNumber = Increment<54>; // number
    type NotOneHundredOneButNumber = Increment<100>; // number
    

    在上述“奇怪”的情况下,自然会做得更好

    type WhatDoYouThink = Increment<5 | 10>; // 6 | 11 👍
    type AndThis = Increment<number>; // number 👍
    

    好吧,希望这能再次帮助你。祝你好运