Typescript:映射联合类型时的类型级数学

2024-04-27

是否可以使用类型级数学来映射打字稿中的联合类型以生成作为第一个联合的函数的新联合?

例如,我想使用现有的联合类型:

type foo = 768 | 1024 | 1280;

为了产生这个并集(每个选项除以 16):

type bar = 48 | 64 | 80;

如果工会的成员数量是灵活的,即,也可以是:
600 | 768 | 1024 | 1280


对此的简短回答是,目前在 TypeScript 中不可能对数值执行任意数学计算文字类型 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types。有一个(相当长期的)开放功能请求:微软/TypeScript#26382 https://github.com/microsoft/TypeScript/issues/26382要求这个。您可能想去那里给它一个????,并且由于其状态是“等待更多反馈”,如果您认为它引人注目,您可能想在那里留下评论,详细说明您的用例。但这可能不会产生太大的影响,所以现在最好假设类型级数学不会很快发生。

您可以通过操作说服编译器执行某种数学运算元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types like [any, any, any]。元组类型有length属性是数字文字,您可以使用可变元组类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#variadic-tuple-types and 递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types更改元组长度。


这意味着您将仅限于对非负整数进行运算(您不能拥有长度为3.14159 or -2),并且由于类型递归的限制约为 ~25 级深度 1000 级深度(UPDATE:TS4.5 已将其增加到约 1000 个级别尾递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#tail-recursion-elimination-on-conditional-types所以现在好一点了,但是当涉及到数字时它仍然很小),很难让事情与大数字一起工作。直观的实现往往只适用于小于 1000 左右的数字。有一些不太直观的实现可以处理数千或数万的数字(请参阅这条评论 https://github.com/microsoft/TypeScript/issues/26223#issuecomment-674514787在相关的 GitHub 问题上),但即使这些也涉及实际构建这些长度的元组,因此可能会使编译器陷入困境。即使你非常聪明,你也可能会写出一些复杂而脆弱的东西。边缘情况是到处.

您的原始代码示例是将小数除以二,这可以使用递归来完成,如下所示:

type DivideByTwo<N extends number, T extends 0[] = []> = N extends any ?
  0 extends [...T, ...T, 0][N] ? T['length'] : DivideByTwo<N, [0, ...T]> : never;

type X = DivideByTwo<6 | 10 | 30>; // type X = 3 | 5 | 15

这是通过取一个数字来实现的N和一个元组T它从空开始([])。如果有两份T连接在一起后跟单个元素,索引处有一个元素N, then T至少有两倍大N,我们只返回长度T。否则,T太小了,所以我们向其中添加一个元素,然后重试。这会将小数字切成两半:如果N is 10, then T变成[], then [0], then [0,0], then [0,0,0], then [0,0,0,0], then [0,0,0,0,0]满足原始检查,所以你得到5.

The N extends any...部分进行操作分配性的 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types超过工会N,所以输入的并集成为输出的并集......所以DivideByTwo<10 | 20> is 5 | 10(按某种顺序)。


但后来你改变了你的例子,将大得多的数字除以十六。为了开始这样做,我必须开始使用重复加倍元组的技巧(这意味着递归深度限制最终看起来像是数字对数的限制,而不是数字本身的限制)。尝试对除数(分子,大数)和除数(分母,这里的 16)都这样做对我来说根本不值得尝试。这是一个硬编码的东西,似乎适用于除以十六:

type Quadruple<T extends any[]> = [...T, ...T, ...T, ...T]

type Explode<N extends number, R extends never[][]> =
  Quadruple<R[0]>[N] extends never ? R : Explode<N, [[...R[0], ...R[0]], ...R]>;

type BinaryBuilder<N extends number, R extends never[][], B extends never[]> =
  Quadruple<[...B, never]>[N] extends never ? B :
  Quadruple<[...R[0], ...B]>[N] extends never ? BinaryBuilder<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, B> :
  BinaryBuilder<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, [...R[0], ...B]>;

type CutTupleInFour<N extends number> = number extends N ? any[] : N extends number ?
  Explode<N, [[never]]> extends infer U ? U extends never[][]
  ? BinaryBuilder<N, U, []> : never : never : never;

type DivideByFour<N extends number> = CutTupleInFour<N>['length']

type DivideBySixteen<N extends number> = DivideByFour<DivideByFour<N>>


type Foo = 768 | 1024 | 1280;
type Bar = DivideBySixteen<Foo>
// type Bar = 64 | 48 | 80

你喜欢?是的,我也一样。即使详细解释它是如何工作的,我也无能为力。我会说它通过除以四两次来工作(当我直接尝试十六次时,编译器陷入困境并且无法在合理的时间内完成),并且它通过构建重复加倍长度的元组来除以四,停止当它有一个太大时,然后将它们连接在一起以形成正确长度的一个。

草图:假设我们应该划分80除以 16。编译器首先除804. 它构建长度元组32, 16, 8, 4, 2, and 1。然后它连接长度16 and 4得到长度之一20。现在又分了204,通过构建长度元组8, 4, 2, and 1。然后它连接长度4 and 1得到长度之一5。结果是5.

但是恶心,丑陋,可怕。在生产中你不想做任何事情。


如果我是你,我会重新检查为什么你希望类型系统为你做这件事而不是你自己做。如果您仍然有充分的理由,那么在 microsoft/TypeScript#26382 上进行游说可能比使用元组跳过障碍更有效。

Playground 代码链接 https://www.typescriptlang.org/play?#code/HYQwtgpgzgDiDGEAEBhATiAXgTwEIFcAXASWEPwEtCKA3ZAbwCgklDsZkARWigEwlzYAKgHcA9gB4AckggAPQhGC8oSYPjAAjCGgA0SIbIVKVSAAwBtALpIAvEmsA+O0hnzFy1SGDYkAfmYWcyMPUwsAOkihfUjw6PMrCykbPwMLAHIAGyUAc0IAC3SbAC4kbho+AWFxaX0LMxioq2dS4Ag6NABuRkC2DiQADRdyysFRSQA2JAAfJABGMxmkAGYzR06kAHpN1nZkIftlpYBWJbnjnoBfHtBIWARkAAkxNDQKTUzsUihvCABBZRSMQkMiUah0JBMQLbJD5QiEGBQYrbHJUfL4TTheBiMCbMAUeBoMRQMQAM0ImyEewAyoSKDAKRQoFB8NBNgAmCbs9nLADETJZEGxYEgZAAtBMAOwAFmOc2lkoAHJKeiw+sgAIr4EC8ND4GDZCSGdwmLw+JwuCJRRpxG3xWJCKyq3b9ACicgNYn40hCprUGm0eiQACVfZ41O0dNYLbZAiwtTq9QaIBJg-VmkkbCbw20Ov4Q0hSu7Pd6pHUreE02YrDaq1Ya0hYsHmt1enskLgKKA0HhKJl+GgfdnTOotDp9KHh6pc1HEg3cGGR5G0DG40gE7r9YaK7h9DO0Bnkovp8v8wvimuN0nt030zbcIes8Yc6fUp3u72KP2dLUC1OHHWNpdqSOhIAAqik4HHhGHTRpBYGFjBoGtMu+i4C0a7viAPYEF+A6-pOz5hIBjaRMBoEQfmCH-vucFUYh+4MahDi3tW94ts66qoEQQhbhApAAGJiPgg5uER04Bjozj2KOgbQTIqTeNg1iIWJoQSWOaD+GuxaZF6KZlg4Fj7vWzj-uRWkIak1HiUhK5zmub5dthn7fqJ+hgXUzRMXmKG+XZrZqu2Iz8IIQkiUOtmyVJLgoDxfGCcJomOBk2TAHkhROm2-QhVU1IUMYSiRep-qadJZQ8KF2DhYOuVhUl0iOI4PTZcgQliC4koTIqZxmOy0pnOyipmN0QX9Lg2HDJVeUFYoRXtc1LAwlxE1afYEwDbM0o9bMw1XD0QA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript:映射联合类型时的类型级数学 的相关文章

随机推荐