GeezerVison is showing github.com — Tuples in rest parameters and spread expressions by ahejlsberg · Pull Request #24897 · microsoft/TypeScript
Conversation
This PR includes the following:
- Expansion of rest parameters with tuple types into discrete parameters.
- Expansion of spread expressions with tuple types into discrete arguments.
- Generic rest parameters and corresponding inference of tuple types.
- Optional elements in tuple types.
- Rest elements in tuple types.
With these features it becomes possible to strongly type a number of higher-order functions that transform functions and their parameter lists (such as JavaScript's bind, call, and apply).
The PR implements the a number of the capabilities discussed in #5453, but with considerably less complexity.
Rest parameters with tuple types
When a rest parameter has a tuple type, the tuple type is expanded into a sequence of discrete parameters. For example the following two declarations are equivalent:
declare function foo(...args: [number, string, boolean]): void;
declare function foo(args_0: number, args_1: string, args_2: boolean): void;
EDIT: With #26676 the type of rest parameter can be a union of tuple types. This effectively provides a form of overloading expressed in a single function signature.
Spread expressions with tuple types
When a function call includes a spread expression of a tuple type as the last argument, the spread expression corresponds to a sequence of discrete arguments of the tuple element types. Thus, the following calls are equivalent:
const args: [number, string, boolean] = [42, "hello", true];
foo(42, "hello", true);
foo(args[0], args[1], args[2]);
foo(...args);
Generic rest parameters
A rest parameter is permitted to have a generic type that is constrained to an array type, and type inference can infer tuple types for such generic rest parameters. This enables higher-order capturing and spreading of partial parameter lists:
declare function bind<T, U extends any[], V>(f: (x: T, ...args: U) => V, x: T): (...args: U) => V;
declare function f3(x: number, y: string, z: boolean): void;
const f2 = bind(f3, 42); // (y: string, z: boolean) => void
const f1 = bind(f2, "hello"); // (z: boolean) => void
const f0 = bind(f1, true); // () => void
f3(42, "hello", true);
f2("hello", true);
f1(true);
f0();
In the declaration of f2 above, type inference infers types number, [string, boolean] and void for T, U and V respectively.
Note that when a tuple type is inferred from a sequence of parameters and later expanded into a parameter list, as is the case for U, the original parameter names are used in the expansion (however, the names have no semantic meaning and are not otherwise observable).
Optional elements in tuple types
Tuple types now permit a ? postfix on element types to indicate that the element is optional:
let t: [number, string?, boolean?];
t = [42, "hello", true];
t = [42, "hello"];
t = [42];
In --strictNullChecks mode, a ? modifier automatically includes undefined in the element type, similar to optional parameters.
A tuple type permits an element to be omitted if it has a postfix ? modifier on its type and all elements to the right of it also have ? modifiers.
When tuple types are inferred for rest parameters, optional parameters in the source become optional tuple elements in the inferred type.
The length property of a tuple type with optional elements is a union of numeric literal types representing the possible lengths. For example, the type of the length property in the tuple type [number, string?, boolean?] is 1 | 2 | 3.
Rest elements in tuple types
EDIT: Updated to reflect change from string* to ...string[] syntax for rest elements.
The last element of a tuple type can be a rest element of the form ...X, where X is an array type. A rest element indicates that the tuple type is open-ended and may have zero or more additional elements of the array element type. For example, [number, ...string[]] means tuples with a number element followed by any number of string elements.
function tuple<T extends any[]>(...args: T): T {
return args;
}
const numbers: number[] = getArrayOfNumbers();
const t1 = tuple("foo", 1, true); // [string, number, boolean]
const t2 = tuple("bar", ...numbers); // [string, ...number[]]
The type of the length property of a tuple type with a rest element is number.
Strong typing of bind, call, and apply
With this PR it becomes possible to strongly type the bind, call, and apply methods on function objects. This is however a breaking change for some existing code so we need to investigate the repercussions.
Fixes #1024.
Fixes #4130.
Fixes #5331.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Kingwl, cartant, zzj3720, chgc, dested, gcnew, olaf89, filipbech, niba, otodockal, and 227 more reacted with thumbs up emoji
Kingwl, zzj3720, MQuy, mweststrate, johannesnagl, j-oliveras, BassT, Bnaya, VitorLuizC, bgoscinski, and 36 more reacted with laugh emoji
Kingwl, pelotom, cartant, arjunyel, zzj3720, otodockal, j-oliveras, TobiaszCudnik, s-ve, MQuy, and 112 more reacted with hooray emoji
Kingwl, johnnyreilly, zzj3720, chgc, j-oliveras, blakeembrey, gabro, MQuy, JoshuaKGoldberg, karol-majewski, and 100 more reacted with heart emoji
All reactions
-
👍
237 reactions
-
😄
46 reactions
-
🎉
122 reactions
-
❤️
110 reactions
|
thanks for your great working🤩
|
rkovalov, vrachels, georgyfarniev, TeoTN, and mahoo12138 reacted with thumbs up emoji
fvilante reacted with laugh emoji
All reactions
-
👍
5 reactions
-
😄
1 reaction
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
This looks incredible and I can't wait to make use of this.
Open-ended tuple types (in progress).
That sounds amazing - this would allow us to type the return value of string.split() as always having at least one element:
split(delimiter: string): [string, string*]
In a world where we have #13778, that would mean the types for destructured split would be correctly inferred:
// [string, string | undefined]
const [package, version] = '[email protected]'.split('@')
The * Kleene star looks a bit weird because it is not used for rest syntax in JS. Maybe spread syntax could be used? This would align with a future generic type spread operator: [string, ...number[]]
The inner array type is spread into the tuple.
|
Kingwl, Cryrivers, jovdb, timocov, ajafff, AlexGalays, rhalff, sergeysova, jaen, dsherret, and 14 more reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Great!,
I think you made a typo in the comment of f1. shouldn't z be of type boolean?
const f1 = bind(f2, "hello"); // (z: boolean) => void
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Great work! Now TypeScript is one step closer to #5453!
Rather than having [string, number*], could we support spread operator in types as well, so we could have something like [string, ...number[]]?
|
jovdb, Strate, sergeysova, goodmind, jaen, Lenne231, esamattis, KiaraGrouwstra, bterlson, SlurpTheo, and 3 more reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Zhongliang Wang (@Cryrivers)
Rather than having [string, number*], could we support spread operator in types as well, so we could have something like [string, ...number[]]?
Or just have multiple rest arguments: (a: string, ...rest: [string, number], ...restLast: string[])
|
jovdb and greg-hornby-roam reacted with confused emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
But tuples can't be infinite by definition?
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
# Conflicts:
# tests/baselines/reference/api/tsserverlibrary.d.ts
# tests/baselines/reference/api/typescript.d.ts
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Bnaya, danielyogel, cah-sam-beran, KiaraGrouwstra, felixfbecker, haltcase, and karthikiyengar reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Felix Becker (@felixfbecker) Zhongliang Wang (@Cryrivers) There are two ways we could go on the syntax for the last element in an open-ended tuple:
[string, number*]
[string, ...number[]]
The latter is longer but certainly makes it easier to see the connection to rest parameters. If we go with the latter, the last element will be required to have an array type. This PR will specifically not permit the equivalent of generic rest parameters in tuples--that would add considerable complexity. But certainly the ...X syntax would better accommodate that feature later on, should we choose to go there.
|
ibezkrovnyi, Lenne231, and fvilante reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
I think the last type being required to be an array is perfectly acceptable.
|
ckknight, KiaraGrouwstra, jovdb, lazarljubenovic, benjamingr, abenhamdine, SlurpTheo, and SpencerPark reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
This is a wonderful step forward!
+1 to the [string, ...number[]] syntax, I feel despite its verbosity it would keep the syntax intuitive to JS users.
Potential extensions needed for the functions andretshurotshka (@goodmind) mentioned:
type Tpl = [string, number];
type MyTpl = [...Tpl, boolean];
(^ or using a generic Tpl, as above)
|
agentcooper, Cryrivers, Akiq2016, SlurpTheo, jameslaneconkling, and ethanresnick reacted with thumbs up emoji
agalazis and chrisfls reacted with heart emoji
All reactions
-
👍
6 reactions
-
❤️
2 reactions
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
I believe that the better choice for the open-ended tuple syntax is [ ... string[] ].
Star has two benefits: arguably better syntax, and preventing generic rest parameters (edit: for now 😝). The latter is solved as Anders Hejlsberg (@ahejlsberg) notes by always requiring the last element to have an array type, which I think is a reasonable restriction.
Syntactically, I guess * looks very foreign to JS developers and that most would prefer a more familiar syntax (this thread is good evidence, thanks everyone!). * also opens up a whole can of worms around which "regexp-type operators" are supported (e.g. can we write [string, string*] as [string+]?). Lastly, if we get full support for variadic types, the ... syntax will have to be supported anyway, and thus we will end up with two syntaxes for the same thing (i.e. [number, string*] and [number, ... string[]] and presumably a lint rule to make one or the other preferred).
I think we should just adopt the javascripty syntax that will be intuitive to those with experience using rest/spread and is a pure subset of existing proposals for variadic types and others.
|
DanielRosenwasser, weswigham, mshustov, ckknight, Cryrivers, codahk, benjamingr, capaj, SlurpTheo, mariusnn, and 2 more reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
T["length"] example
type Take<N extends number, T extends any[], R extends any[]=[]> = {
0: Reverse<R>,
1: Take<N, Tail<T>, Cons<Head<T>, R>>
}[T extends [] ? 0 : R["length"] extends N ? 0 : 1];
export type Group<N extends number, T extends any[], R1 extends any[]=[], R2 extends any[]=[]> = {
0: Reverse<R2>,
1: Group<N, T, [], Cons<Reverse<R1>, R2>>,
2: Group<N, Tail<T>, Cons<Head<T>, R1>, R2>
}[T extends [] ? R1 extends [] ? 0 : 1 : (R1["length"] extends N ? 1 : 2)];
export type Drop<N extends number, T extends any[], R extends any[]=[]> = {
0: T,
1: Drop<N, Tail<T>, Cons<Head<T>, R>>
}[T extends [] ? 0 : R["length"] extends N ? 0 : 1];
|
TylorS reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 22, 2018
This was referenced May 1, 2019
|
Hello - this fix apparently makes this possible: #24897 but I can't for the life of me figure out how and that thread has been closed now.
I am trying to implement a signal that is used in the following way:
const mySignal = Signal<string, number, SomeType>()
mySignal.emit('sdf', 123, someObj)
So basically emit uses rest arguments that are typed when the signal is created.
Could someone point me in the right direction regarding what the generics for this might look like?
Many thanks!
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
1 more reviewer
Reviewers whose approvals may not affect merge requirements
Uh oh!
There was an error while loading. Please reload this page.
Milestone
No milestone
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|