Skip to content

Commit a8bd31c

Browse files
committed
Finish cleanup
1 parent 2b149df commit a8bd31c

File tree

1 file changed

+87
-118
lines changed

1 file changed

+87
-118
lines changed

pages/Tuple Kinds.md

+87-118
Original file line numberDiff line numberDiff line change
@@ -72,51 +72,12 @@ V = [number, number, string, string]
7272
...U = []
7373
```
7474

75-
Note that non-rest parameters can be typed as tuple kinds.
76-
The examples section contains several examples of this.
77-
78-
The previous examples show tuples used for variadic kinds.
79-
Note that, if JavaScript supported named arguments, then objects could also be expected to be spread into function calls, and Typescript would also need variadic object kinds.
80-
Currently [a stage 2 ECMAScript proposal](https://github.com/sebmarkbage/ecmascript-rest-spread) supports object spreading, but only within object destructuring contexts. Here is an example:
81-
In Javascript:
82-
83-
```js
84-
function namedCurry(f, obj1) {
85-
return obj2 => f({...obj1, ...obj2});
86-
}
87-
function f({a, b, c, d}) {
88-
}
89-
namedCurry(f, {a: 12, b: 34})({c: 56, d: 78});
90-
```
91-
92-
And now with types in TypeScript:
93-
94-
```ts
95-
interface FirstHalf {
96-
a: string;
97-
b: number;
98-
}
99-
interface SecondHalf {
100-
c: string;
101-
d: boolean;
102-
}
103-
interface Total extends FirstHalf, SecondHalf {
104-
}
105-
function namedCurry(f: Total => void, obj1: FirstHalf): SecondHalf => void {
106-
return obj2 => f({...obj1, ...obj2});
107-
}
108-
```
109-
110-
So, actually this example doesn't need kinds. That's because named parameters don't exist in Ecmascript 2015: the `arguments` array is an array, not an object.
111-
11275
## Syntax
11376

11477
The syntax of a variadic kind variable is `...T` where *T* is an identifier that is by convention a single upper-case letter, or `T` followed by a `PascalCase` identifier.
11578
Variadic kind variables can be used in a number of syntactic contexts:
11679

117-
### Variadic kind variables
118-
119-
Variadic kinds can be bound in the usual location for type variable binding, including functions and classes:
80+
Variadic kinds can be bound in the usual location for type parameter binding, including functions and classes:
12081

12182
```ts
12283
function f<...T,...U>() {}
@@ -132,26 +93,13 @@ function makeTuple<...T>(ts:...T): ...T {
13293
return ts;
13394
}
13495
function f<...T,...U>(ts:...T): [...T,...U] {
135-
let us: ...U = makeTuple('hello', 'world'); // note that U is constrained to [string,string] here
96+
// note that U is constrained to [string,string] in this function
97+
let us: ...U = makeTuple('hello', 'world');
13698
return [...ts, ...us];
13799
}
138100
```
139101

140-
Tuples are instances of variadic kinds, so they continue to appear wherever tuple type annotations were previously allowed:
141-
142-
```ts
143-
function f<...T>(ts:...T): [...T,string,string] {
144-
let us: [string,string] = makeTuple('hello', 'world'); // note the annotation can be inferred here
145-
return [...ts, ...us];
146-
}
147-
148-
let tuple: [number, string] = [1,'foo'];
149-
f<[number,string],[string,string]>(tuple);
150-
```
151-
152-
### Variadic kind operations
153-
154-
Variadic kind variables, like type variables, are quite opaque and are not present at runtime.
102+
Variadic kind variables, like type variables, are quite opaque.
155103
They do have one operation, unlike type variables.
156104
They can be concatenated with other kinds or with actual tuples.
157105
The syntax used for this is identical to the tuple-spreading syntax, but in type annotation location:
@@ -161,14 +109,27 @@ let t1: [...T,...U] = [...ts,...uProducer<...U>()];
161109
let t2: [...T,string,string,...U,number] = [...ts,'foo','bar',...uProducer<...U>(),12];
162110
```
163111

112+
Tuple types are instances of variadic kinds, so they continue to appear wherever type annotations were previously allowed:
113+
114+
```ts
115+
function f<...T>(ts:...T): [...T,string,string] {
116+
// note the type of `us` could have been inferred here
117+
let us: [string,string] = makeTuple('hello', 'world');
118+
return [...ts, ...us];
119+
}
120+
121+
let tuple: [number, string] = [1,'foo'];
122+
f<[number,string]>(tuple);
123+
```
124+
164125
## Semantics
165126

166127
A variadic kind variable represents a tuple type of any length.
167128
Since it represents a set of types, we use the term 'kind' to refer to it, following its use in type theory.
168129
Because the set of types it represents is tuples of any length, we qualify 'kind' with 'variadic'.
169130

170131
Therefore, declaring a variable of variadic tuple kind allows it to take on any *single* tuple type.
171-
Like type variables, kind variables can only be declared by functions, which then allows them to be used inside the body of the function:
132+
Like type variables, kind variables can only be declared as parameters to functions, classes, etc, which then allows them to be used inside the body:
172133

173134
```ts
174135
function f<...T>(): ...T {
@@ -182,15 +143,15 @@ Calling a function with arguments typed as a variadic kind will assign a specifi
182143
f([1,2,"foo"]);
183144
```
184145

185-
Assigns the tuple type `[number,number,string]` to `...T`.
186-
So in this application of `f`, `let a:...T` is equivalent to `let a:[number,number,string]`.
187-
However, because the type of `a` is not known when the function is written, the elements of the tuple cannot be referenced.
188-
Only concatenation will work.
146+
Assigns the tuple type `...T=[number,number,string]`...T`.
147+
So in this application of `f`, `let a:...T` is instantiated as `let a:[number,number,string]`.
148+
However, because the type of `a` is not known when the function is written, the elements of the tuple cannot be referenced in the body of the function.
149+
Only creating a new tuple from `a` is allowed.
189150
For example, new elements can be added to the tuple:
190151

191152
```ts
192-
function cons<H,...Tail>(car: H, cdr: ...Tail): [H,...Tail] {
193-
return [car, ...cdr];
153+
function cons<H,...Tail>(head: H, tail: ...Tail): [H,...Tail] {
154+
return [head, ...tail];
194155
}
195156
let l: [number, string, string, boolean];
196157
l = cons(1, cons("foo", ["baz", false]));
@@ -213,9 +174,22 @@ let tail: [number, boolean] = ["baz", false];
213174
let l = cons(1, cons("foo", tail));
214175
```
215176

177+
Additionally, variadic kind variables can be inferred when concatenated with types:
178+
179+
```ts
180+
function car<H,...Tail>(l: [H, ...Tail]): H {
181+
let [head, ...tail] = l;
182+
return head;
183+
}
184+
car([1, "foo", false]);
185+
```
186+
187+
Here, the type of `l` is inferred as `[number, string, boolean]`.
188+
Then `H=number` and `...Tail=[string, boolean]`.
189+
216190
### Limits on type inference
217191

218-
Note that concatenated kinds cannot be inferred because the checker cannot guess where the boundary between two kinds should be:
192+
Concatenated kinds cannot be inferred because the checker cannot guess where the boundary between two kinds should be:
219193

220194
```ts
221195
function twoKinds<...T,...U>(total: [...T,string,...U]) {
@@ -250,29 +224,31 @@ function rotate(l:[...T, ...U], n: number): [...U, ...T] {
250224
let rest: ...U = l.slice(n);
251225
return [...rest, ...first];
252226
}
253-
rotate<...[boolean, boolean, string], ...[string, number]>([true, true, 'none', 12', 'some'], 3);
227+
rotate<[boolean, boolean, string], [string, number]>([true, true, 'none', 12', 'some'], 3);
254228
```
255229
256230
This function can be typed, but there is a dependency between `n` and the kind variables: `n === ...T.length` must be true for the type to be correct.
257-
I'm not sure whether this is code that should actually be supported.
258-
231+
I'm not sure whether this is code that should actually be allowed.
259232
260-
### Extensions of tuple types
233+
### Empty tuple types
261234
262235
Typescript does not allow users to write an empty tuple type.
263-
However, this proposal requires variadic kinds to be bound to a empty tuple.
236+
However, this proposal requires variadic kinds to be bindable to a empty tuple.
264237
So Typescript will need to support empty tuples, even if only internally.
265238
266239
### Semantics on classes and interfaces
267240
268241
The semantics are the same on classes and interfaces.
269242
243+
TODO: There are probably some class-specific wrinkles in the semantics.
244+
270245
## Examples
271246
272247
Most of these examples are possible as fixed-argument functions in current Typescript, but with this proposal they can be written as variadic.
248+
Some, like `cons` and `concat`, can be written for homogeneous arrays in current Typescript but can now be written for heteregoneous tuples using tuple kinds.
273249
This follows typical Javascript practise more closely.
274250
275-
### cons/concat
251+
### Return a concatenated type
276252
277253
```ts
278254
function cons<H,...T>(head: H, tail:...T): [H, ...T] {
@@ -284,83 +260,76 @@ function concat<...T,...U>(first: ...T, ...second: ...U): [...T, ...U] {
284260
cons(1, ["foo", false]); // === [1, "foo", false]
285261
concat(['a', true], 1, 'b'); // === ['a', true, 1, 'b']
286262
concat(['a', true]); // === ['a', true, 1, 'b']
263+
264+
let start: [number,number] = [1,2]; // type annotation required here
265+
cons(3, start); // == [3,1,2]
287266
```
288267
289-
### apply
268+
### Concatenated type as parameter
290269
291270
```ts
292-
function apply<...T,U>(f: (args:...T) => U, args: ...T): U {
293-
return f(args);
271+
function car<H,...T>(l: [H,...T]): H {
272+
let [head, ...tail] = l;
273+
return head;
274+
}
275+
function cdr<H,...T>(l: [H,...T]): ...T {
276+
let [head, ...tail] = l;
277+
return ...tail;
294278
}
279+
280+
cdr(["foo", 1, 2]); // => [1,2]
281+
car(["foo", 1, 2]); // => "foo"
295282
```
296283
297-
### curry
284+
### Variadic functions as arguments
298285
299286
```ts
300-
function curry<...T,...U,V>(f: (...args:[...T,...U]) => V, ts:...T): (us: ...U) => V {
301-
return us => f(...ts, ...us);
287+
function apply<...T,U>(f: (...args:...T) => U, args: ...T): U {
288+
return f(...args);
302289
}
303-
```
304290

305-
### pipe/compose
291+
function f(x: number, y: string) {
292+
}
293+
function g(x: number, y: string, z: string) {
294+
}
295+
296+
apply(f, [1, 'foo']); // ok
297+
apply(f, [1, 'foo', 'bar']); // too many arguments
298+
apply(g, [1, 'foo', 'bar']); // ok
299+
```
306300
307301
```ts
308-
function pipe<...T,U,V>(f: (ts:...T) => U, g: (u:U) => V): (args: ...T) => V {
309-
return ...args => g(f(...args));
302+
function curry<...T,...U,V>(f: (...args:[...T,...U]) => V, ...ts:...T): (...us: ...U) => V {
303+
return us => f(...ts, ...us);
310304
}
305+
let h: (...us: [string, string]) = curry(f, 1);
306+
let i: (s: string, t: string) = curry(f, 2);
307+
h('hello', 'world');
311308
```
312309

313310
```ts
314311
function compose<...T,U,V>(f: (u:U) => U, g: (ts:...T) => V): (args: ...T) => V {
315312
return ...args => f(g(...args));
316313
}
314+
function first(x: number, y: number): string {
315+
}
316+
function second(s: string) {
317+
}
318+
let j: (x: number, y: number) => void = compose(second, first);
319+
j(1, 2);
320+
317321
```
318322
319323
TODO: Could `f` return `...U` instead of `U`?
320324
321-
### decorators
325+
### Decorators
322326
323327
```ts
324328
function logged<...T,U>(target, name, descriptor: { value: (...T) => U }) {
325329
let method = descriptor.value;
326-
327330
descriptor.value = function (...args: ...T): U {
328-
console.log(args);
329-
method.apply(this, args);
331+
console.log(args);
332+
method.apply(this, args);
330333
}
331334
}
332335
```
333-
334-
### Tuple copy implemented as `Array.slice()`
335-
336-
A function-based `slice` is simple to write:
337-
338-
```ts
339-
slice<...T>(tuple: ...T): ...T;
340-
```
341-
342-
A class-based `slice` requires the containing class to bind a variadic kind variable:
343-
344-
```ts
345-
interface Tuple<...T> {
346-
slice(): ...T;
347-
}
348-
```
349-
350-
### Spread arguments that don't directly match a rest parameter
351-
352-
That is, `car` and `cdr`:
353-
354-
```ts
355-
function car<H,...T>(l: [H,...T]): H {
356-
let [head, ...tail] = l;
357-
return head;
358-
}
359-
function cdr<H,...T>(l: [H,...T]): ...T {
360-
let [head, ...tail] = l;
361-
return ...tail;
362-
}
363-
364-
cdr(["foo", 1, 2]); // => [1,2]
365-
car(["foo", 1, 2]); // => "foo"
366-
```

0 commit comments

Comments
 (0)