-
Notifications
You must be signed in to change notification settings - Fork 782
Experiment: Quantified Types #2434
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
base: main
Are you sure you want to change the base?
Conversation
|
This seems like a duplicate of microsoft/TypeScript#14466. |
Yes this PR implements a quantified type! Should have linked this issue in the PR description but forgot that there's an issue for it. |
|
The given
This is literally my go to explanation of what revererse mapped types are useful for :P So, at the very least, I'd try to use a different motivating example to justify this. Don't get me wrong though, I definitely feel the idea is interesting :) |
|
Oh I didn't realize it would look that easy with reverse mapped types haha (I was thinking more in terms of the self-type-checking approach)... In that case the example of yours I converted here will make a good motivating example for quantified types... type QueryOptions = <K extends string, T, U> {
queryKey: K
queryFn?: (ctx: { queryKey: K }) => Promise<T> | T;
select?: (t: T) => U;
}
declare const useQueries:
(queries: QueryOptions[]) => void
useQueries([
{
queryKey: "hello",
queryFn: ctx => Number(ctx.queryKey),
select: n => typeof n === "number"
}
])This one probably can't be done with reversed mapped types or self-type-checking types. I'll have to do more work on this PR to make this example work tho haha but I'm up for it! |
Reverse mapped types could easily~ do the example above if those would get considered: microsoft/TypeScript#53017 and microsoft/TypeScript#54029 . Using them like this would look a little bit out of ordinary but I feel like it's actually pretty straightforward once one groks you can structure their code this way. The example above could be implemented like this: TS playground. It's likely though that some extra work would have to be done to make it work beyond those 2 mentioned PRs. |
|
Yeah I think in theory you could pull it off with reverse mapped types and with some enhancements in the checker but then quantified types are nicer to read/understand... so I'm a bit biased haha |
Imagine you have a function
consumeLayer...Okay good. How what if we need to consume two layers? We can have another parameter but then we'll also need another type parameter because the children of those two layers might be of different shapes. So it'd look like this...
Okay so far so good. Now what if we had to accept an array of layers? How do we dynamically create "n" generics? Surely one generic won't make the cut because each layer can have a different shape of children and having one generic will squash those child shapes into a union...
How do we solve this?
The answer is we can't. (Unless we employ some hacky workarounds that end up reducing type safety). But let's take a step back and think... Do we even need the generic on the function? Surely the layer type needs a type parameter to express it's structure but does that have to be on the function? What if TypeScript allowed us to express the structure like this...
If this was possible we could simply type the layers as
Layer[]and call it a day...This is precisely what this PR enables... You can create a type parameter out of thin air and have a richer way to express your types. Formally such types are called universally quantified types.
As a matter of fact this also unintentionally allows you to create "Self-Type-Checking" types... For example here's the
CaseInsensitivetype...Any self-type-checking type can be expressed as a quantified type, but not the other way round. And quantified types being superset of self-type-checking types is totally unintentional, I did not want to implement self-type-checking types again haha.
This
consumeLayersproblem is something I actually encountered while working at something else and I thought it would be fun to implement quantified types in typescript. So this PR is just an experiment and doesn't intend to be merged in (nor does it handle all edge cases). So feel free to close it. But I'd love to hear what thoughts y'all have on this. I'd also love to see what other use-cases people can come up with.Thanks for reading.