Note: I started a discussion on Github about this subject.
I have a zip function, for now it is typed for iterables of the same type T
. I would like to have this typed for arbitrary mixed input type but still conserving the matching output type, for example, if the input type [Iterable<T>, Iterable<U>]
I want the output type to be Iterable<[T, U]>
. Is it possible to have this for arbitrary input size? I basically want to say, if you have this list of type as input you'll have them as output.
Here is the current version of my zip:
export function *zip<T>(...iterables:Array<Iterable<T>>): Iterable<Array<T>> {
const iterators = iterables.map(iterable => iter(iterable));
while(true){
const items = iterators.map(iterator => iterator.next());
if (items.some(item => item.done)){
return;
}
yield ((items.map(item => { return item.value }): Array<any>): Array<T>);
}
}
export function *iter<T>(iterable:Iterable<T>): Iterator<T> {
yield* iterable;
}
Current best solution by AndrewSouthpaw:
declare function zip<A, B>(Iterable<A>, Iterable<B>): Iterable<[A, B]>;
declare function zip<A, B, C>(Iterable<A>, Iterable<B>, Iterable<C>): Iterable<[A, B, C]>;
declare function zip<A, B, C, D>(Iterable<A>, Iterable<B>, Iterable<C>, Iterable<D>): Iterable<[A, B, C, D]>;
export function *zip<T>(...iterables:Array<Iterable<T>>): Iterable<Array<T>> {
const iterators = iterables.map(iterable => iter(iterable));
while(true){
const items = iterators.map(iterator => iterator.next());
if (items.some(item => item.done)){
return;
}
yield ((items.map(item => { return item.value }): Array<any>): Array<T>);
}
}
It works as expected when called with 4, 3 or 2 iterables, when called with 5 or more arguments flow will simply say that zip can only be called with 4 or less arguments. Of course we could add as many function signature as we like to get it to work for 5, 6 or any number N of arguments, but that would require to declare N distinct signatures (which is a bit ugly). On the other hand this strategy does not allow to have an unbounded number of arguments (like the spread operator does). I'm still looking for that.
This raised a more general question, is there any language in which this exists?
I really have the feeling that this can be done in theory (not necessarily in flow), on the other hand I can't recall of a statically typed language in which I've done/seen that (I would also be interested in seeing this kind of type checking in any language).
To be a bit more specific, my feeling is that if you have a type checking system in which (by definition) all types are statically known (any variable has a known type x
) then function f: Array<Iterable<x>> -> Iterable<Array<x>>
is always called on a known type x
. Therefore we should be able to statically decide what type f
will return given x
(whether x
is a single generic type or a list of generic types).
The same goes for the function itself, if you have a type x
as input, then you only need to check that your function preserve type x
.
Maybe this needs to be defined recursively in some languages, that would also be interesting to see.
Here is the working solution. All credit goes to jbrown215 from Flow team, he found the idea of using $ReadOnlyArray<mixed>
here:
export function *zip<T: $ReadOnlyArray<mixed>>(...iterables:Array<Iterable<T>>): Iterable<Array<T>> {
const iterators = iterables.map(iterable => iter(iterable));
while(true){
const items = iterators.map(iterator => iterator.next());
if (items.some(item => item.done)){
return;
}
yield ((items.map(item => { return item.value }): Array<any>): Array<T>);
}
}
export function *iter<T>(iterable:Iterable<T>): Iterator<T> {
yield* iterable;
}