Repository navigation
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.
Activity
RyanCavanaugh commentedon Feb 5, 2019
Previously suggested in similar forms
#16918
#19106
#17574
I think this proposal tries to sort of do all of them at once and would be best tackled individually.
I can't say for sure that it's fatal, but the syntactic confusion problem here is overwhelming for the case where the function type is generic (ignoring the position of the annotation for the moment):
It's extremely unclear here whether the type arguments are introducing new types with those names, or referencing existing types by those names.
Similarly here:
My reading is that this is a non-generic function with two (implicit)
anyarguments.Like other names, type arguments need to be introduced in an unambiguous syntax that makes it reasonably clear to the reader what's going on. Right now, in a type annotation position,
T<U>is unambiguously a reference toU, not an introducing declaration.MattiasMartens commentedon Feb 5, 2019
Ryan Cavanaugh (@RyanCavanaugh) Thanks for taking a look at this.
Having looked at the tickets you referenced, I do agree that #16918 and #19106 address the first part of the problem (whole-typed functions) and #17574 is a more thoroughgoing solution to the second part (deferred generics, which I believe is very similar to what maseedu is calling "arbitrary parametric polymorphism").
Going over it now I do think I was mixing up some concerns. I'll split them up below to see if any of them are worth investigating.
Setting aside polymorphism completely, whole-typing functions is intuitive in the case of arrow functions:
It can also be done with function declarations:
But that's a pretty ugly syntax (and also not exactly equivalent because of hoisting). My favourite syntax of the ones I suggested is
I can propose that over on #16918 and dispense with it here.
Then there's arbitrary polymorphism. #17574 doesn't seem to propose a syntax, but it does seem like some kind of syntax would be necessary for some of the cases the issue is describing (e.g.
—in the pie-in-the-sky scenario here, what would one use in place of
<any>?). This is where I think theinferkeyword might be useful to borrow.However—and this is the core motivation for my suggestion—a thoroughgoing solution for arbitrary polymorphism looks a long way off (because of problems discovered investigating #9366, #9949, also see #24626; I believe this concern would affect any complete implementation of #16918 but please correct me if I am wrong), while a special syntax for whole-typing functions is comparatively easy. It just happens that in the particular case where we're typing a function declaration, this:
is a restatement of this already supported syntax:
With the added benefit of type-checking with a reusable interface.
My point is that if we can agree on
Then we have an opportunity to get the benefits of arbitrary polymorphism on whole-typed functions without having to tackle the hard problems of solving the general case—because what I'm proposing is equivalent to what the engine is already doing.
typescript-bot commentedon Feb 7, 2019
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.