Ввод функции "цепочка" генератора

У меня есть следующая функция:

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

Он принимает список итерируемых объектов и создает генератор, который последовательно выдает результат из каждого из них.

Я не уверен, как правильно ввести его для поддержки итераций смешанного типа. Если мои входные данные похожи на Iterable<X>, Iterable<Y> и т. д., то результат должен быть Iterable<X | Y>. Как написать это для вариативного аргумента?

Ссылка на игровую площадку

Это похоже на пример использования экзистенциальных типов.

vighnesh153 06.02.2023 10:56

Этот ответ может быть полезен для вашего вопроса: stackoverflow.com/a/67842566/3977134 - с его помощью это может сработать для вас: chain<T extends any[]>(...iters: T): Iterable<T[number]>

r3dst0rm 06.02.2023 11:02

Что-то вроде это соответствует вашим потребностям? Если это так, я могу превратить это в ответ. Если нет, то что я пропустил?

jsejcksn 06.02.2023 11:51

@jsejcksn: я действительно этого не понимаю, поэтому буду признателен за ответ с объяснением.

gog 06.02.2023 12:48
^ @gog Хорошо, я разместил один.
jsejcksn 06.02.2023 14:12
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
3
5
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

См. этот выпуск GitHub для получения дополнительной информации: microsoft/TypeScript#41646 — Разрешить общие типы yield*

Используя ограниченный параметр универсального типа, тип возвращаемого значения вашей функции-генератора может быть получен из ее аргументов.

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

В приведенной выше сигнатуре функции общий Iters должен быть присваиваемым типу, который представляет собой массив элементов Iterable<unknown> только для чтения. Итак, каждый аргумент в оставшемся параметре iters должен присваиваться Iterable<unknown>. Это позволит компилятору определить полученный тип каждого итерируемого аргумента.

Вот пример его применения к реализации, которую вы показали, а затем его использование с несколькими примерами функций из вашей ссылки на игровую площадку, чтобы увидеть предполагаемый тип возвращаемого значения:

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<number> | Iterable<string>, и что его использование в цикле 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

Код в TS Playground


Заключительные мысли:

Если не использовать утверждение типа для возвращаемого значения второго примера, возникает ошибка компилятора:

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;
};

Я думаю, что это вызвано ограничением анализа потока управления TypeScript (но, возможно, я ошибаюсь, и кто-то другой может дать больше ясности).

Решение состоит в том, чтобы утвердить тип значения, например:

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
};
type YieldedFromIterable<I extends Iterable<unknown>> = I extends Iterable<infer T> ? T : never; достаточно. yield* как for..of не могу иметь дело с Iterator<T> если это не IterableIterator<T>. И IterableIterator<T>, и Generator<T> наследуются от Iterable<T>.
Thomas 06.02.2023 14:32
^ @Thomas Спасибо за комментарий к этому наблюдению — я согласен и именно это учитывал при написании. Я решил оставить других в союзе для будущих читателей, которые могут найти этот вопрос, пытаясь решить немного другую проблему (и могут не подумать об изменении его в противном случае).
jsejcksn 06.02.2023 14:36

Другие вопросы по теме