Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make zip and zipAll typings generic #432

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from

Conversation

canac
Copy link
Contributor

@canac canac commented Apr 5, 2021

I believe I have found a way to type zip and zipAll with an arbitrary number of arguments. Let's get another set of eyes on this, but if it works, it's much cleaner than special cases for the two and three argument cases.

@codecov
Copy link

codecov bot commented Apr 5, 2021

Codecov Report

Merging #432 (85f5337) into trunk (8f5f25f) will not change coverage.
The diff coverage is n/a.

❗ Current head 85f5337 differs from pull request most recent head d28a36e. Consider uploading reports for the commit d28a36e to get more accurate results
Impacted file tree graph

@@           Coverage Diff           @@
##            trunk     #432   +/-   ##
=======================================
  Coverage   99.21%   99.21%           
=======================================
  Files         221      221           
  Lines        1792     1792           
=======================================
  Hits         1778     1778           
  Misses         14       14           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8f5f25f...d28a36e. Read the comment docs.

@canac
Copy link
Contributor Author

canac commented May 1, 2021

@conartist6 Are you interested in getting this merged? Let me know if there's anything I can fix or change.

@conartist6
Copy link
Member

conartist6 commented May 1, 2021 via email

@conartist6
Copy link
Member

This doesn't look to me like it should work, but the tests are passing. How can it work if you aren't ever actually inferring anything from tuple types?

@canac
Copy link
Contributor Author

canac commented May 1, 2021

Sorry, I don't fully understand your question, but Unwrap converts each Wrappable<T> to T. Zip then uses Unwrap on each array in the sources array to convert the array of wrapped types into the array of raw types.

@conartist6
Copy link
Member

conartist6 commented May 1, 2021

Ooooh yep when I dig into the way the test case is passing it's because the result of zip is IterableIterator<any>. It's hard to avoid that kind of false positive in type tests.

I don't exactly understand how Typescript came up with any, but I'm not exactly surprised either.

I think what you should be leveraging is rest elements in tuple types. The problem that I think you're going to hit is that I can't find a way that works to map tuple types. The problem with using the mapped object type syntax that you were using is that the result looks kind of a little like a tuple, but also is wildly different from what typescript thinks of as a tuple type. Here is what that looks like in the playground.

Screen Shot 2021-05-01 at 6 34 24 PM

@canac
Copy link
Contributor Author

canac commented May 1, 2021

Thanks for checking it. Where is it any for you? For me it's the correct type in the test:

Screen Shot 2021-05-01 at 5 57 18 PM

@canac
Copy link
Contributor Author

canac commented May 1, 2021

And regarding rest tuple types, yes, I am using rest tuple types in that definition. I just split it out into the Zip type for greater readability. An equivalent definition would be:

declare function zip<Sources extends Wrappable<unknown>[]>(
  ...sources: Sources
): IterableIterator<{
  [index in keyof Sources]: Unwrap<Sources[index]>;
}>;

@conartist6
Copy link
Member

Right but what you wrote essentially boils down to

declare function zip<T>(...sources: Array<Iterable<T>>);

So sources is in an array type, not a tuple type. Once the tuple type becomes an array type you've lost the distinction between the types of any individual keys.

@canac
Copy link
Contributor Author

canac commented May 1, 2021

Yes, I agree that sources becomes an array, not a tuple. I don't see how the array type loses the distinction between the types of the individual keys. In this snippet, for example, the type of result is [number, string].

function identity<T extends Array<unknown>>(...input: T): T {
    return input;
}

const result = identity(1, 'a'); // type is [number, string]

Playground link

Sorry, if I'm missing something. Can you give me a code example where this breaks down? Or back to the original issue, show where the result is IterableIterator<any> in the typing tests?

@conartist6
Copy link
Member

conartist6 commented May 2, 2021

So let's talk about a simpler version of the identity function:

declare function identity<T>(t: T): T;

The way generics work is that the caller understands what the real type of T, but the definition of the identity function knows nothing about it. So yes, what escapes the black box is what went in, but that doesn't make it any less of a black box. If you try to return T['foo'] obviously that will fail because the type in T could actually be any type at all, e.g. null. So the extends clause restricts valid inputs so that type expressions which require certain assumptions to be true can work. You could write T extends { foo: any } and now T['foo'] would be a valid return type.

So what you have to keep in mind is that when you write extends you are guaranteeing something that is true about the type, but that is all you know about it inside the black box. You can't extract information about the real type from what you declared in extends, because the point of extends is to mask the real type from you, not to expose it.

@conartist6
Copy link
Member

Oh man, to be honest I still can't tell if my logic is entirely straight on this.

@conartist6
Copy link
Member

OOOOooooh. I did something stupid. I'm so sorry. I was using my IDE to look at the types internal to the type test, and it was saying any because it thought there was an error in zip.d.ts. That was just the language server failing to reload all the files the patch changed though. Now I can see it working correctly, and I think my logic about how generics was based on how flow treats them not on how Typescript does...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants