cef*_*efn 5 arrays recursion tuples typescript typescript-typings
我最近遇到了一个 API,其中的数组由任意数量的重复元组构成。我尝试了一些替代方法来实现它,但遇到了具有无限递归或只是不表达约束的类型的问题。
我如何能够按照下面的情况输入 RepeatedTuple?
//Possibly a recursive conditional way to define RepeatedTuple?
// https://github.com/microsoft/TypeScript/pull/40002
type Example = RepeatedTuple<[string,number]>;
const example0:Example = [];
const example1:Example = ["hello",1];
const example2:Example = ["hello",1,"hello",2];
const example3:Example = ["hello",1,"hello",2,"hello",3];
const example4:Example = ["hello",1,"hello",2,"hello",3,"hello",4];
Run Code Online (Sandbox Code Playgroud)
TypeScript 中没有任何特定类型来表示“由任意数量的副本组成的元组string, number”。在这种情况下,您可以获得的最接近的是一些涵盖您实际预期的用例的有限联合,或者一些可用于验证候选元组的自引用通用约束。
请注意,虽然后者似乎允许完全任意长度的元组,但对递归的推理支持很脆弱,并且根据 TypeScript 的版本可能会严重扩展。因为有限并集与元组的长度成线性比例,所以实际上你会比递归走得更远。
\nRepeatedTuple以下是一种最多可重复写入 40 次的方法:
type _R<T extends readonly any[], U extends readonly any[]> = \n readonly [] | readonly [...U, ...T];\ntype RepeatedTuple<T extends readonly any[]> =\n _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T,\n _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T,\n _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T, _R<T,\n _R<T, _R<T, _R<T, _R<T, readonly []>>>>>\n >>>>>>>>>>>>>>>>>>>\n >>>>>>>>>>>>>\n >>>;\nRun Code Online (Sandbox Code Playgroud)\n它使用可变元组来连接数组,我们只需手动编写 40 次联合或连接操作。如果需要,您可以添加此内容,尽管最终这会达到某种限制。让我们看看它是如何工作的:
\ntype Example = RepeatedTuple<[string, number]>\nconst example0: Example = [];\nconst example1: Example = ["hello", 1];\nconst example2: Example = ["hello", 1, "hello", 2];\nconst example3: Example = ["hello", 1, "hello", 2, "hello", 3];\nconst example4: Example = ["hello", 1, "hello", 2, "hello", 3, "hello", 4];\n\nconst badExample0: Example = ["hello"] // error!\n// Source has 1 element(s) but target requires 80.\nconst badExample1: Example = ["hello", "goodbye"]; // error!\n// Type \'string\' is not assignable to type \'number | undefined\'.\nconst badExample2: Example = ["hello", 1, "hello"]; // error!\n// Source has 3 element(s) but target requires 80.\nconst badExample3: Example = ["hello", 1, "hello", 2, false]; // error!\n// Type \'false\' is not assignable to type \'string | undefined\'.\nconst badExample4: Example = ["hello", 1, "hello", 2, "hello"]; // error!\n// Source has 5 element(s) but target requires 80.\n\n\nconst limits: Example = ["", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,\n "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,\n "", 0, "", 0, "", 0, "", 0, "", 0, "", 0]; // okay\nRun Code Online (Sandbox Code Playgroud)\n这看起来不错。即使是相对较长的limits示例也有效。失败情况下的一些错误消息有点奇怪,因为编译器在解释错误原因时重点关注 tuple-of-length-80。但它按预期工作。但是,如果您创建了长度为 82 或更大的元组,则会失败。
为了完整起见,让我们探讨一下通用约束方法:
\ntype ValidRepeatedTuple<T extends readonly any[], U extends readonly any[]> =\n U extends readonly [] ? U :\n readonly [...T, ...(U extends readonly [...T, ...infer R] ? ValidRepeatedTuple<T, R> : [])];\n\nconst asValidRepeatedTuple = <T extends readonly any[]>() =>\n <U extends readonly any[]>(u: ValidRepeatedTuple<T, U>) => u;\nRun Code Online (Sandbox Code Playgroud)\n这是使用递归条件类型来检查候选元组U与 tuple-to-repeat 的串联T。如果U是 的有效重复T,则将U扩展ValidRepeatedTuple<T, U>。否则,ValidRepeatedTuple<T, U>将出现一个“附近”的有效元组,以便错误消息给出更合理的提示,说明如何修复错误。
请注意,添加泛型意味着需要指定泛型类型参数。为了使开发人员不必完全手动执行此操作,我添加了辅助函数,asValidRepeatedTuple()以便U可以进行推断。此外,由于您想要手动指定T,我们需要创建asValidRepeatedTuple()一个柯里化函数,因为目前无法对microsoft/TypeScript#26242中请求的排序进行部分类型参数推断;在带有类型参数和 的单个泛型函数调用中,您要么需要手动指定两者,要么让编译器推断两者。柯里化允许您在编译器 infer 时手动指定,但代价是需要进行额外的函数调用。TUTU
那么,这有点复杂……它有效吗?让我们来看看:
\nconst asExample = asValidRepeatedTuple<[string, number]>();\n\nconst example0 = asExample([]);\nconst example1 = asExample(["hello", 1]);\nconst example2 = asExample(["hello", 1, "hello", 2]);\nconst example3 = asExample(["hello", 1, "hello", 2, "hello", 3]);\nconst example4 = asExample(["hello", 1, "hello", 2, "hello", 3, "hello", 4]);\n\nconst badExample0 = asExample(["hello"]); // error!\n// Source has 1 element(s) but target requires 2.\nconst badExample1 = asExample(["hello", "goodbye"]); // error!\n// Type \'string\' is not assignable to type \'number\'.\nconst badExample2 = asExample(["hello", 1, "hello"]); // error!\n// Source has 3 element(s) but target requires 4.\nconst badExample3 = asExample(["hello", 1, "hello", 2, false]); // error!\n// Type \'boolean\' is not assignable to type \'string\'.\nconst badExample4 = asExample(["hello", 1, "hello", 2, "hello"]); // error!\n// Source has 5 element(s) but target requires 6.\nRun Code Online (Sandbox Code Playgroud)\n是的,看起来不错。错误消息比以前的版本更好,因为它们提到了一个比编译器从有限联合中选择的元组更“附近”的元组。(它不是说“你的长度3元组不好,你应该做到80”,而是说“你应该做到” 4。)
但这里最大的缺点是递归限制:
\nconst limits = asExample(["", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,\n "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0, "", 0,\n "", 0, "", 0, "", 0, "", 0, "", 0, "", 0]); // error!\n// Type instantiation is excessively deep and possibly infinite.(2589)\nRun Code Online (Sandbox Code Playgroud)\n长度为 46 的元组太长,因为编译器递归太多。有限并集,看起来更受限制,实际上更适合处理较长的元组。您可以通过手动展开一些递归来解决此问题,从而将限制扩展到更大的范围。但它不太可能达到手动工会的程度。
\n您也许能够编写一个不太简单的递归通用约束,通过在递归的每个步骤中将元组加倍或不加倍,该约束随元组的长度以对数方式缩放。看看这个 GitHub 问题评论以了解类似的技术。这可能适用于很长的元组,但是人们很难理解它在做什么(甚至比这里已经存在的东西更难),而且我还没有让它工作。
\n或者,您可以尝试利用对递归条件类型的尾递归消除的支持,但在我的尝试中,我还没有得到很好的推断,并且调用asExample()倾向于推断any[]或(string | number)[]您希望看到的地方就像是[string, number, string, number]。我尝试了大约六种不同的方法,但没有一个对我有用......所以我放弃了。
最后,即使您完善了这一点,请注意它只能真正用于验证候选特定元组。编译器将无法遵循通用元组的逻辑。因此,例如,在一个接受ValidRepeatedTuple<T, U>某些泛型的函数内部U,编译器将完全不知道这意味着什么:
function hmm<U extends readonly any[]>(t: ValidRepeatedTuple<[string, number], U>) {\n t[0].toUpperCase() // compiler thinks it\'s a string, but hey, it might be undefined\n t[1].toFixed() // compiler thinks it\'s a number, but hey, it might be undefined\n t[2]?.toUpperCase() // error! compiler thinks it\'s undefined, but hey, it might be a string\n}\nRun Code Online (Sandbox Code Playgroud)\n对于大型旧工会来说,情况稍微好一些:
\nfunction hmm(t: Example) {\n t[0]?.toUpperCase() // okay\n t[1]?.toFixed() // okay\n t[2]?.toUpperCase() // okay\n for (let i = 0; i < t.length / 2; i++) {\n t[2 * i]?.toUpperCase(); // error\n t[2 * i + 1]?.toFixed(); // error\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n尽管您可以看到它并不真正理解您可能正在执行的操作类型,因此 \xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f。
\n