The code in this article and an implementation of zipLongest are in this gist.
The built-in zip function is ubiquitous in python. It allows highly readable iteration over multiple iterables:
def naturals():
    n = 1
    while True:
        yield n
        n += 1
for n, c in zip(naturals(), "Hello!"):
    print(n, c)
# 1 H
# 2 e
# 3 l
# 4 l
# 5 o
# 6 !
Now, with Mapped Types in typescript, we can do the same thing, all while keeping our lovely types too!
With mapped types, we can make an Iterableify type, which will take a type, and change it so that its members are of type Iterable:
type Iterableify<T> = { [K in keyof T]: Iterable<T[K]> }
For example
type T1 = Iterableify<string[]> // Iterable<string>[]
type T2 = Iterableify<{a: number, b: string}> // {a: Iterable<number>, b: Iterable<string>}
type T3 = Iterableify<[number, string][]> // Iterable<[number, string]>[]
We can then make a simple zip function like so:
function* zip<T extends Array<any>>(
    ...toZip: Iterableify<T>
): Generator<T> {
    // Get iterators for all of the iterables.
    const iterators = toZip.map(i => i[Symbol.iterator]())
    while (true) {
        // Advance all of the iterators.
        const results = iterators.map(i => i.next())
        // If any of the iterators are done, we should stop.
        if (results.some(({ done }) => done)) {
            break
        }
        // We can assert the yield type, since we know none
        // of the iterators are done.
        yield results.map(({ value }) => value) as T
    }
}
Since the mapped types affect typescript tuples as well, we can now do something like this:
for (const [a, b] of zip([1, 2, 3], "abc")) {
    // type of a is number!
    // type of b is string!
    // This has no compilation errors!
    console.log(b.repeat(a))
}
 

 
    
Top comments (1)
Nice,
zipin python is really convenient, thank you for this type safe implementation in typescript!