如何为解析器组合子库定义支持任意长度元组的类型安全map函数?
mapN Parser Combinator with Arbitrary Tuple Length Absolutely! TypeScript's advanced generics support lets us define this mapN function in a fully generic way that works with any length of parser tuple while preserving complete type safety. Here's how to implement it:
Generic Type Definition
First, let's update the MapN type to handle arbitrary-length parser tuples:
type Parser<T> = () => T; // Generic mapN type that adapts to any parser tuple length type MapN = { <T extends readonly Parser<any>[], Z>( parsers: T, fn: (...args: { [K in keyof T]: T[K] extends Parser<infer U> ? U : never }) => Z ): Parser<Z>; };
How This Type Works
Let's break down the key parts:
T extends readonly Parser<any>[]: Constrains the inputparsersto be a tuple (or array) ofParserinstances. Usingreadonlyensures compatibility with both regular arrays and readonly tuple literals.{ [K in keyof T]: T[K] extends Parser<infer U> ? U : never }: This mapped type extracts the return type of eachParserin the tuple. For every indexKinT, we infer the typeUthat the correspondingParserreturns, building a new tuple of those types.(...args: ...) => Z: The functionfnaccepts arguments matching the extracted tuple of parser return types, and returns a value of typeZ. The final result is aParser<Z>that wraps this logic.
Test with Your Examples
Let's verify this works with your original test cases (including the previously commented-out ones):
// Dummy parser implementations for testing const string: Parser<string> = () => "hello"; const number: Parser<number> = () => 42; // Your existing implementation (with a small runtime escape hatch) const mapN: MapN = (parsers, fn) => { return () => fn(...parsers.map(p => p()) as any); }; // All these will have perfect type inference! const p1 = mapN([string, number], (a, b) => [a, b] as const); // Type: Parser<[string, number]> const p2 = mapN([string, number, number], (a, b, c) => [a, b, c] as const); // Type: Parser<[string, number, number]> const p3 = mapN([string, number, string, string], (a, b, c, d) => [a, b, c, d] as const); // Type: Parser<[string, number, string, string]> const p4 = mapN([string, number, string, number, number], (a, b, c, d, e) => [a, b, c, d, e] as const); // Type: Parser<[string, number, string, number, number]>
Quick Note on the Implementation
The as any in the runtime code is a tiny escape hatch—TypeScript can't fully verify that the parsed values match the tuple types at runtime, but the type definition guarantees compile-time safety for how you use mapN. If you want stricter runtime checks, you could add validation logic, but the type system already ensures your function fn receives the correct argument types.
内容的提问来源于stack exchange,提问作者Dogbert




