【问题标题】:Typing the generator "chain" function输入生成器“链”函数
【发布时间】:2023-02-06 21:24:43
【问题描述】:

我有以下功能:

function* chain(...iters) {
    for (let it of iters)
        yield* it
}

它接受一个可迭代对象列表并创建一个生成器,该生成器从每个迭代器中按顺序产生。

我不确定如何正确输入它以支持混合类型的可迭代对象。如果我的输入类似于 Iterable<X>Iterable<Y> 等,那么结果应该是 Iterable<X | Y>。如何为可变参数编写这个?

Playground Link

【问题讨论】:

  • 这看起来像是 existential types 的用例。
  • 这个答案可能对你的问题有帮助:stackoverflow.com/a/67842566/3977134 - 使用它,这对你有用:chain<T extends any[]>(...iters: T): Iterable<T[number]>
  • this之类的东西能满足你的需求吗?如果是这样,我可以把它变成一个答案。如果没有,我错过了什么?
  • @jsejcksn:我不是很明白,所以如果有解释的答案,我们将不胜感激。
  • ^@gog 好的,我发了一个。

标签: typescript generator


【解决方案1】:

有关详细信息,请参阅此 GitHub 问题: microsoft/TypeScript#41646 - Allow generic yield* types

通过使用 constrained generic type parameter,生成器函数的返回类型可以从它的参数中派生出来。

type Chain = <Iters extends readonly Iterable<unknown>[]>(
  ...iters: Iters
) => Iters[number];

在上面的函数签名中,泛型 Iters 必须可分配给 Iterable&lt;unknown&gt; 元素的只读数组类型。因此,其余参数 iters 中的每个参数都必须可分配给 Iterable&lt;unknown&gt;。这将允许编译器推断每个可迭代参数的产生类型。

下面是一个将它应用于您展示的实现的示例,然后将它与您的 playground 链接中的几个示例函数一起使用,以查看推断的返回类型:

declare function test1(): Iterable<number>;
declare function test2(): Iterable<string>;

const chain: Chain = function* (...iters) {
  for (let it of iters) yield* it;
};

const iter = chain(test1(), test2());
    //^? const iter: Iterable<number> | Iterable<string>

for (const value of iter) {}
         //^? const value: string | number

您可以看到推断的返回类型是 Iterable&lt;number&gt; | Iterable&lt;string&gt;,并且在 for...of 循环中使用它会产生一个生成值,该值是并集中每个可迭代对象的生成类型的并集。

我认为这已经根据您的问题标准产生了令人满意的结果,但实际返回类型仍可以改进以更准确地表示返回的迭代器本身。

通过使用类型实用程序(改编自链接的 GitHub 问题中的内容)从可迭代类型中提取产生的类型:

type YieldedFromIterable<
  I extends
    | Iterable<unknown>
    | Iterator<unknown>
    | IterableIterator<unknown>
    | Generator<unknown>
> = I extends
  | Iterable<infer T>
  | Iterator<infer T>
  | IterableIterator<infer T>
  | Generator<infer T>
    ? T
    : never;

...可以为函数创建替代返回类型:

type Chain = <Iters extends readonly Iterable<unknown>[]>(
  ...iters: Iters
) => Iterable<YieldedFromIterable<Iters[number]>>;

与第一个函数签名的返回类型(它是每个可迭代参数的联合)并列,这个是单个可迭代的,它产生一个从每个参数派生的联合值——使用它看起来像这样:

const chain: Chain = function* (...iters) {
  for (let it of iters) yield* it as any;
};

const iter = chain(test1(), test2());
    //^? const iter: Iterable<string | number>

for (const value of iter) {}
         //^? const value: string | number

Code in TS Playground


结论:

不对第二个示例的生成值使用 type assertion 会导致编译器错误:

const chain: Chain = function* (...iters) { /*
      ~~~~~
Type '<Iters extends readonly Iterable<unknown>[]>(...iters: Iters) => Generator<unknown, void, undefined>' is not assignable to type 'Chain'.
  Call signature return types 'Generator<unknown, void, undefined>' and 'Iterable<YieldedFromIterable<Iters[number]>>' are incompatible.
    The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
      Type 'IteratorResult<unknown, void>' is not assignable to type 'IteratorResult<YieldedFromIterable<Iters[number]>, any>'.
        Type 'IteratorYieldResult<unknown>' is not assignable to type 'IteratorResult<YieldedFromIterable<Iters[number]>, any>'.
          Type 'IteratorYieldResult<unknown>' is not assignable to type 'IteratorYieldResult<YieldedFromIterable<Iters[number]>>'.
            Type 'unknown' is not assignable to type 'YieldedFromIterable<Iters[number]>'.(2322) */
  for (let it of iters) yield* it;
};

我认为这是由 limitation of TypeScript's control flow analysis 引起的(但也许我错了,其他人可以提供更清晰的信息)。

一种解决方案是断言值的类型,如下所示:

const chain: Chain = function* (...iters) {
  for (let it of iters) yield* it as Iterable<YieldedFromIterable<typeof iters[number]>>; // ok
};

// OR

const chain: Chain = function* (...iters) {
  for (let it of iters) yield* it as any; // ok
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-03
    • 2016-04-06
    • 1970-01-01
    • 2020-12-28
    • 2017-03-22
    • 1970-01-01
    • 1970-01-01
    • 2021-12-28
    相关资源
    最近更新 更多