-
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
Improve typings of Array.map when called on tuples #29841
Comments
@DanielRosenwasser The arity problem can be solved now that we can map tuples. Not sure about the performance, but not having a ton of overloads might improve it a bit interface Array<T> {
map<TThis extends Array<T>, U>(this: TThis, fn: (v: T) => U): { [K in keyof TThis]: U }
}
function tuple<T extends any[]>(...a: T) {
return a;
}
let x = tuple(1, 2).map(v => v.toString()); //[string, string]
let o = tuple(1, 2, 3).map(n => ({ n })); |
Yes, I opened this issue in light of TypeScript 3.1 mappable tuple and array types. So maybe things have changed? |
Also from what I see #16223 reverted a lot more than just changes to This issue requests improve just |
@dragomirtitian, I believe the new overload you wrote above does break for Array subtypes. RegExp matching makes for a nice example |
Now that variadic tuple types are added, this should be a whole lot easier to support. Any chance on seeing this in a release any time soon? |
I thought so, too, but couldn't figure out a way. Even with instantiation expressions around the corner (#47607 (comment)), it still seems impossible. AFAICT the core problem is that generics (e.g. a generic callback function for A higher-kinded type is to the type system what a higher-order function is to functions. For instance, So functions can cross these boundaries, but generic types cannot. Therefore we lose the ability to dynamically infer the function return type in dependence of the variable function arguments. |
I was trying to preserve the tuple sizes for as well using function lengths<const T extends readonly string[]>(...names: T) {
return names.map(n => n.length);
}
const result = lengths('foo', 'baaaar', 'baaaaaaaaaaaz');
// number[] |
And of course, I just found a workaround for my issue above right after I posted. In my case above I can use function lengths<const T extends readonly string[]>(...names: T) {
return names.map(n => n.length) as { [K in keyof T]: number; };
}
const result = lengths('foo', 'baaaar', 'baaaaaaaaaaaz');
// readonly [number, number, number] |
no function overloads. no passing types as generics. instead, we rely on two things: * [Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html), which turns out to be useful on [converting tuples](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-1.html#mapped-types-on-tuples-and-arrays). (as an example, see: - https://stackoverflow.com/questions/68180531/how-to-use-variadic-tuple-types-in-typescript - https://stackoverflow.com/questions/72091158/typescript-is-it-possible-to-define-a-variadic-function-that-accepts-different - microsoft/TypeScript#29841 ) This means that by doing: ```typescript function htmlMultipleFn<T extends string[]>(htmlStrings: [...T]): { [I in keyof T]: DeterminedNode<T[I]> }; ``` This receives a 'tuple' of strings, and returns *an object that takes key of the tuple, and a value of some different computation* -- which is effectively a map of an array. What's better, is that it also 'remembers' the exact length, because it is considered as a tuple, not an array -- that's why we have `[...T]` for arg type instead of `T`. * [Const Type Parameter](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#const-type-parameters) Now, with only the one above, the following works: ```typescript const result = htmlMultipleFn(['<div>']) // [HTMLDivElement] ``` but this doesn't: ```typescript const [notDivEl] = htmlMultipleFn(['<div>']) // Node ``` To make T stay as a tuple instead of an array, we apply const type parameter: ```typescript function htmlMultipleFn<const T extends string[]>(htmlStrings: [...T]): { [I in keyof T]: DeterminedNode<T[I]> }; ``` NOTE we had to remove tests which received node type generics, because we are not using those types anymore: ```typescript // 🚫 We can't do this any more //const [divEl, pEl] = htmlMultipleFn<[HTMLDivElement, HTMLParagraphElement]>([ // "<div>Hi there,</div>", // "<p>I am here</p>", //]) ``` To force the type, we need to do type assertion: ```typescript // ✅ Do this instead const [divEl, pEl] = htmlMultipleFn( ["<div>Hi there,</div>", "<p>I am here</p>"] ) as [HTMLDivElement, HTMLParagraphElement] ```
Search Terms
Suggestion
Using Array.map on a tuple should return a tuple instead of an array. In one of my projects I could achieve that using
I haven't encountered any negative side effects.
Use Cases
This can be useful when you want to use tuples as a fixed length array.
Examples
This is currently an error:
https://www.typescriptlang.org/play/#src=type%20Vec3D%20%3D%20%5Bnumber%2C%20number%2C%20number%5D%3B%0D%0Alet%20vec%3A%20Vec3D%20%3D%20%5B1%2C%202%2C%203%5D%3B%0D%0Alet%20scaledVec%3A%20Vec3D%20%3D%20vec.map(x%20%3D%3E%202%20*%20x)%3B
But with the proposed change it would not be an error:
https://www.typescriptlang.org/play/#src=declare%20interface%20Array%3CT%3E%20%7B%0D%0A%20%20%20%20map%3CU%3E(callbackfn%3A%20(value%3A%20T%2C%20index%3A%20number%2C%20array%3A%20T%5B%5D)%20%3D%3E%20U%2C%20thisArg%3F%3A%20any)%3A%20%7B%20%5BK%20in%20keyof%20this%5D%3A%20U%20%7D%3B%0D%0A%7D%0D%0A%0D%0Atype%20Vec3D%20%3D%20%5Bnumber%2C%20number%2C%20number%5D%3B%0D%0Alet%20vec%3A%20Vec3D%20%3D%20%5B1%2C%202%2C%203%5D%3B%0D%0Alet%20scaledVec%3A%20Vec3D%20%3D%20vec.map(x%20%3D%3E%202%20*%20x)%3B
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: