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[]]>
.