Description
Search Terms
generic function, parametric function, function type parameter, function type argument
The problem is closely related to #26043, but the proposed solution is different.
Suggestion
Currently, there is no way of explicitly providing type arguments to generic functions. For example, consider the following function:
function box<T>(value: T) {
return { value };
}
TypeScript correctly infers the return type { value: T }
. However, it's not possible to derive the return type for a particular T
, say number
, at least not without ugly workarounds such as creating new functions just to fix the type arguments:
type Box = ReturnType<typeof box>; // { value: unknown }
type Box<T> = ReturnType<typeof ???>; // can't propagate T from Box to box
const numBox = (n: number) => box(n); // ugly workaround
type NumBox = ReturnType<typeof numBox>;
So I suggest to provide a syntax that allows to do just that: fix type arguments of generic functions:
type Box<T> = ReturnType<typeof box<T>>;
type NumBox = ReturnType<typeof box<number>>;
This syntax resembles the existing one for providing type arguments when calling a function:
const b = box<number>(2);
With this proposal implemented, interpreting such a call could be split into two parts:
First fix the type argument of box to number
, which yields a function that takes a number.
Second, apply that function to 2
.
Hence, it could also be written with parenthesis: (box<number>)(2)
.
I wouldn't suggest doing that in practice, but it shows that this proposal increases composability of generic functions.
Use Cases
I tried to come up with a way to implement discriminated unions more succintly. The idea is to start with factory functions and then combine the return types:
function val<V>(value: V) {
return { type: "Val", value } as const;
}
function plus<V>(left: V, right: V) {
return { type: "Plus", left, right } as const;
}
function times<V>(left: V, right: V) {
return { type: "Times", left, right } as const;
}
type Expr<V> = ReturnType<typeof val<V>>
| ReturnType<typeof plus<V>>
| ReturnType<typeof times<V>>;
With this approach, you would get convenient factory functions for building instances, without duplicating the object structure.
But it doesn't work, because it relies on the new syntax I just proposed.
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.