-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Add a way to prefer narrowing array to tuple if possible #27179
Comments
You can force an array to be treated as a tuple by creating a function function tuple<Args extends any[]>(...args: Args): Args { return Args; }
const pair = tuple(1, "string");
// pair is inferred as [number, string] The rest of it seems a bit more tricky. Mapped types don't work properly with tuples so it might need language level support to convert one tuples type to another (sort of like array.map but for type level tuples) An even more general approach might be to allow recursion in types so we can use conditional types to write our own mapping logic. EDIT: Apparently, your Promise.race example doesn't need any language changes. interface PromiseConstructor {
race<Ts extends Array<any>>(
promises: Ts
): UnwrappedArray<Ts>
} This works fine. The first example works too using the tuple function. declare function zipLongest<Ts extends Array<Iterable<any>>>(iterables: Ts): Iterable<ZipUnwrapped<Ts>>;
const strs = ['foo', 'bar', 'baz', 'boz']
const nums = [1, 2, 3, 4]
const zipPair = tuple(strs, nums);
const z = zipLongest(zipPair) Basically, instead of doing |
I think you can already do this with features currently in typescript@next. In particular, the work in #26063 enables your scenario. type Iterablized<T> = { [K in keyof T]: Iterable<T[K]> };
declare function zipLongest<T extends any[]>(iterables: Iterablized<T>): Iterable<T>;
declare let triple: [Iterable<string>, Iterable<number>, Iterable<string[]>];
let zippedTriple = zipLongest(triple); // Iterable<[string, number, string[]> |
With respect to explicit syntax for creating tuples from array literals, we're tracking and debating that in #16656. Meanwhile, with #24897 (in TS 3.0) you can easily add the following convenience function: function tuple<T extends any[]>(...elements: T) {
return elements;
} and you then have a very low cost general way of creating tuples. For example: const t1 = tuple(1, "hello"); // [number, string]
const t2 = tuple(1, tuple(2, 3)); // [number, [number, number]]
const t3 = tuple(1, [2, 3]); // [number, number[]]
const t4 = tuple(); // [] |
I've been trying it with typescript@next and the mapped types isn't quite the problem, strictly speaking the definition above works, it's just not particularly useful when doing it inline with inferred types e.g.: for (const [a, b] of zipLongest([strings, numbers]) {
// Inferred type of both a and b are string | number
} Which is what I'm proposing improving. Basically someway to say that if This already works (in typescript@next) when the argument is variadic rather than as an array directly e.g.: type Iterablized<T> = { [K in keyof T]: Iterable<T[K] | undefined> };
declare function zipLongest<Ts extends Array<any>>(...iterables: Ts): Iterablized<Ts>
const strs = ['foo', 'bar', 'baz']
const nums = [1, 2, 3]
// Correctly inferred to be Iterable<[string | undefined, number | undefined]>
const z = zipLongest(strs, nums) However this can't be used as I'm extending the const fillers = [() => '', () => 0]
for (const [a, b] of zipLongest([strs, nums], fillers)) {
} |
@Jamesernator One trick that does work is to ensure that the contextual type for the array literal includes a tuple type. Once that is the case we'll infer tuple types: declare function zipLongest<Ts extends Iterable<any>[] | [Iterable<any>]>(iterables: Ts): Iterable<ZipUnwrapped<Ts>>; |
Oh that's useful, that should probably be added into the handbook somewhere as I don't think I would've ever figured that out. |
@DanielRosenwasser @weswigham FYI since we were discussing this the other day. Note that if we put this in the handbook, we should recommend using |
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow. |
The JS types were getting out of hand, with the input graph being complicated enough as it was, plus the added headaches from component interfaces and all the internal types in the span splitting algorithms. The logical solution is to throw strict static types at it, so that's what I did. Note: still a gaping hole with the `Tk` type of CodeBlock, which I thought I could get away with keeping abstracted from the SPANTYs, but unfortunately `candidate` uses it to check if things are clickable. Will have to move that type up to MainController somehow. --- Aside: a lot of the pain of manual annotations in this port came from tuples being inferred as a union list type. There's no nice solution unfortunately, as for me both `as const` and `tuple()` functions don't work: the former becuase I then have to put `readonly` everywhere, and the latter just doesn't convince the typechecker. See <microsoft/TypeScript#27179> for some discussion. The other source of type annotations was the need to label collection types for some reason, especially in `reduce`, and especially the ad hoc ones where I really didn't want to write the type of the accumulator. I think I've been spoiled by Haskell, where I've come to respect just how predictable and powerful the inference is, and how fantastic the errors are (mostly thanks to the keyed union types and non-duck-typing to be fair).
Search Terms
tuple narrow, prefer tuple
Suggestion
This is a meta-suggestion over the top of some of the cases in #16896 and #26113, basically at current it's quite repetitive to specify overloads for tuple types and we tend to wind up things like this:
We can already specify the type if we know only tuples are going to be input e.g.:
In this case the type of
z
is correctly inferred to beIterable<[string, number]>
, but if we remove the type declaration onzipPair
then we get that the inferred type ofz
isIterable<(number | string)[]>
.Basically what I propose is adding some way to specify that an array should be narrowed to the most precise tuple possible if we can.
There's a couple approaches that might be used:
Approach 1
Use the
[...TupleParam]
spread syntax already mentioned in #26113 and when this syntax is seen narrow to the best tuple we could get. So the above declaration would become:This has the downside that it might for any
Ts
in[Some, Tuple, Params, ...Ts]
to be narrowed even when the wider type was what was intended.Approach 2
Add some new syntax that specifies that if a certain value can be narrowed into a tuple then we should do so:
The syntax isn't really all that important. The main downside of this approach is that we need two overloads, one for the narrowed tuple and one for the non-narrowed tuple. Additionally extra syntax would need to be supported and maintained.
Examples
Other than the above example another example that would benefit is the builtin
lib.promise.d.ts
which specifies overloads in this repetitive overloading pattern.We could write something like
Promise.all
like so:And inference would just work.
Checklist
My suggestion meets these guidelines:
EDIT: Realized
Promise.race
was supposed to bePromise.all
oops.The text was updated successfully, but these errors were encountered: