Description
Search Terms
- functions
- higher-order functions
- generic types
- type inference
- type parameters
- infer
Suggestion
This comprises two suggestions in one as I want to leave it up you to decide which is better value-for-effort. The suggestions are also compatible with each other.
1: typing a function
When declaring a function, I want to be able to declare the type of the function as a whole, not just the type of the parameters or the return value. I want this to be compatible with generic types just like the existing function declaration syntax is.
Example:
type MyStupidType<A, B> = (arg: A, transform: (sameArg: A) => B) => B
function myStupidFunction(arg, transform) {
return transform(arg);
}: MyStupidType<A, B>;
The above is idiomatic, but clunky, so an alternative suggested syntax is:
<MyStupidType<A, B>> function myStupidFunction(arg, transform) {
return transform(arg);
}
Since the function may not even have to reference the generic types explicitly (as in the example), it may even be possible to drop them, like so:
<MyStupidType> function myStupidFunction(arg, transform) {
return transform(arg);
}
Or like so:
<MyStupidType<>> function myStupidFunction(arg, transform) {
return transform(arg);
}
Or like so:
<MyStupidType<?, ?>> function myStupidFunction(arg, transform) {
return transform(arg);
}
2: typing a const generically
The usual workaround for the problem I'm facing is to use arrow functions instead:
const myStupidFunction: MyStupidType<any /* ;( */, (sameArg: any) => any /* ;( */> = (arg, transform) => transform(arg)
As you can see, this doesn't work with generics, because I have to make the function non-generic in order to declare it in this way.
The proposed change is to allow the const
to remain generic using the infer
keyword:
const myStupidFunction: MyStupidType<infer A, infer B> = (arg, transform) => transform(arg);
...or, as suggested in 1), just dropping them if they do not need to be directly referenced:
const myStupidFunction: MyStupidType = (arg, transform) => transform(arg);
Use Cases
I ran into the problem described in #9366 while playing with types describing reducers (in an attempt to building up to understanding transducers):
type SimpleReducer<T, V> = (acc: V, next: T) => V;
type AbstractReduce<T, V> = (
iterable: Iterable<T>,
reducer: SimpleReducer<T, V>,
initialValue: V,
) => V;
const reduceIterable: AbstractReduce = (
iterable,
reducer,
initialValue,
) => {
let out = initialValue;
for (let next of iterable) {
out = reducer(out, next);
}
return out;
};
This of course doesn't work because AbstractReduce
is being referenced with no type parameters. But my implementation here is still too abstract for me to want to supply them.
However, this works:
function reduceIterableFn<T, V>(
iterable: Iterable<T>,
reducer: SimpleReducer<T, V>,
initialValue: V
): V {
let out = initialValue;
for (let next of iterable) {
out = reducer(out, next);
}
return out;
}
This would be fine except that it means I can't use my interface, because there's no way to tell TypeScript that a function implements a generic interface (as opposed to its parameters or return type considered individually).
The overall problem of deferring resolution of generics until a function is called turns out to be extremely difficult under the constraints of TypeScript; but what I'm proposing (hopefully) is not and it suffices for my use case.
Examples
The above sections show examples. When a developer writes a function that needs to implement a type, they can use the above syntax to allow TypeScript to extend the current behaviour around function declarations to function declarations that implement types.
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.