I wrote an iterator class and a method for zipping two iterators which takes 1 argument and its type declaration looks like this:
zip<B>(other: Iterable<B> | Iterator<B>): ItIterator<[T, B]>
where T is the type of this.next().value.
However I can't grasp how to write it so it takes any number of arguments and returns an iterator over a tuple such that
ItIterator.prototype.zip.call([1][Symbol.iterator](), ['a'], [false])
would return ItIterator<[number, string, boolean]>
Is there a way to do this?
CodePudding user response:
Here's the approach I'd take:
declare class ItIterator<T> {
zip<B extends any[]>(
...other: { [I in keyof B]: Iterable<B[I]> | Iterator<B[I]> }
): ItIterator<[T, ...B]>;
}
The idea is that zip() is generic in B, the tuple type of the element types of the other iterables. I mean that if you call zip(x, y, z) where x is an Iterable<X>, y is an Iterable<Y>, and z is an Iterable<Z>, then the type argument B will be [X, Y, Z].
This is accomplished by having the rest parameter tuple type of other be a mapped tuple type over B.
Then the output type is an ItIterator<> of the variadic tuple type [T, ...B], where we prepend T to the tuple of B.
Let's test it out:
declare const i: ItIterator<string>;
const y = i.zip([1], [true], [new Date(), new Date()]);
// const y: ItIterator<[string, number, boolean, Date]>
Looks good. Note that I wouldn't try to support
const z = ItIterator.prototype.zip.call([1][Symbol.iterator](), ['a'], [false]);
// const z: ItIterator<[any, ...any[]]>
because the typing support for the call() method of functions does not work well with functions that are themselves generic, and you end up getting just the constraint of ItIterator<[any, ...any[]]>.
