【问题标题】:Defining a type for this function that works on arbitrary length tuples为这个函数定义一个适用于任意长度元组的类型
【发布时间】:2020-12-30 19:01:10
【问题描述】:

我正在开发一个解析器组合器库,我需要实现一个 map 函数,该函数采用元组中的 N 解析器,以及一个采用这些 N 参数并返回解析器的函数,该解析器解析为函数的返回类型。

<A, B, Z>(ps: [a: Parser<A>, b: Parser<B>], f: (a: A, b: B) => Z): Parser<Z>
<A, B, C, Z>(ps: [a: Parser<A>, b: Parser<B>, c: Parser<C>], f: (a: A, b: B, c: C) => Z): Parser<Z>
// etc

我正在寻找一种方法来为任意数量的解析器定义 map 函数的类型。

我已经有了这个函数的实现,除了类型。

最小的复制:

type Parser<T> = () => T;

const string: Parser<string> = null as any;
const number: Parser<number> = null as any;

type MapN = {
    <A, B, Z>(ps: [a: Parser<A>, b: Parser<B>], f: (a: A, b: B) => Z): Parser<Z>,
    <A, B, C, Z>(ps: [a: Parser<A>, b: Parser<B>, c: Parser<C>], f: (a: A, b: B, c: C) => Z): Parser<Z>,
}

const mapN: MapN = null as any;

const p1 = mapN([string, number], (a, b) => [a, b] as const);
const p2 = mapN([string, number, number], (a, b, c) => [a, b, c] as const);
// const p3 = mapN([string, number, string, string], (a, b, c, d) => [a, b, c, d] as const);
// const p4 = mapN([string, number, string, number, number], (a, b, c, d, e) => [a, b, c, d, e] as const);

有没有办法为任意数量的参数定义这个函数,同时保持类型安全?

Playground

【问题讨论】:

    标签: typescript


    【解决方案1】:

    是的,您可以通过递归元组迭代来做到这一点。

    这是通过迭代 P(解析器的元组)并一次推断第一个元素来实现的(参见 P extends readonly [Parser&lt;infer R&gt;, ...(infer Tail)] 行)。

    然后我们将其传递回ExtractParserReturnTuple 的第二个结果 (T) 参数,直到我们在 P extends readonly [Parser&lt;infer R&gt;] 处达到输入数组 (P) 中仅剩一个元素的基本情况,此时我们合并 @ 987654327@ 进入结果元组T

    这适用于任意数量的参数。不幸的是,您需要向输入解析器和内部函数的返回类型添加 as const 声明。据我了解,这是 readonly 工作方式的限制 - 也许还有另一种我不知道的方式。

    type Parser<T> = () => T;
    
    const string: Parser<string> = null as any;
    const number: Parser<number> = null as any;
    
    type ExtractParserReturnTuple<P extends readonly Parser<any>[], T extends any[] = []> = 
        P extends readonly [Parser<infer R>]
            ? readonly [...T, R]
        : P extends readonly [Parser<infer R>, ...(infer Tail)]
            ? Tail extends readonly Parser<any>[]
                ? ExtractParserReturnTuple<Tail, [...T, R]>
                : readonly []
            : readonly [];
    
    
    function mapN<P extends readonly Parser<any>[], R>(parsers: P, func: (...args: ExtractParserReturnTuple<P>) => R): R {
        return null as unknown as R;
    }
    
    const p1 = mapN([string, number] as const, (a, b) => [a, b] as const);
    const p2 = mapN([string, number, number] as const, (a, b, c) => [a, b, c] as const);
    const p3 = mapN([string, number, string, string] as const, (a, b, c, d) => [a, b, c, d] as const);
    const p4 = mapN([string, number, string, number, number] as const, (a, b, c, d, e) => [a, b, c, d, e] as const);
    

    Playground link

    【讨论】:

    • 我可能会坚持生成代码,因为类型推断确实很有用,但很高兴知道有一个可能的解决方案(虽然这需要我一段时间才能理解它是如何工作的)!谢谢!
    • 是的,这不是最好的阅读方式,因为确保写入覆盖更具可读性和可维护性。希望将来会有更好的方法来处理此类案件。如果您想对任何特别的事情进行解释,请告诉我
    • 所以我开始探索在两个地方都必须添加as const 的想法并想出了这个:stackblitz.com/edit/mu3mif?file=index.ts(TS Playground URL 太长,无法粘贴到这里)。在我看来,推断的类型与您的代码相同。你怎么看?我错过了什么吗?
    • 哦,太好了!是的,干净多了,我没有意识到您可以将这样的映射类型与数组一起使用,请随意发布作为答案并接受!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-19
    • 1970-01-01
    • 1970-01-01
    • 2017-10-05
    • 1970-01-01
    • 2021-04-23
    相关资源
    最近更新 更多