У меня есть следующая функция:
function* chain(...iters) {
for (let it of iters)
yield* it
}
Он принимает список итерируемых объектов и создает генератор, который последовательно выдает результат из каждого из них.
Я не уверен, как правильно ввести его для поддержки итераций смешанного типа. Если мои входные данные похожи на Iterable<X>
, Iterable<Y>
и т. д., то результат должен быть Iterable<X | Y>
. Как написать это для вариативного аргумента?
Этот ответ может быть полезен для вашего вопроса: stackoverflow.com/a/67842566/3977134 - с его помощью это может сработать для вас: chain<T extends any[]>(...iters: T): Iterable<T[number]>
Что-то вроде это соответствует вашим потребностям? Если это так, я могу превратить это в ответ. Если нет, то что я пропустил?
@jsejcksn: я действительно этого не понимаю, поэтому буду признателен за ответ с объяснением.
См. этот выпуск 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
Заключительные мысли:
Если не использовать утверждение типа для возвращаемого значения второго примера, возникает ошибка компилятора:
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>
.
Это похоже на пример использования экзистенциальных типов.