You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Typescript does not implicitly infers types when providing function as arguments? #17520
When providing identity function to map, it's return type is incorrect(see x2), while providing anonymous function as a callback leads to a correct output type(see x1).
constmap=<T,U>(f: (x: T)=>U,arr: T[]): U[]=>{returnarr.map((val)=>f(val));}// x1 type is number[]constx1=map(x=>x,[1,2,3]);constidentity=<T>(x: T)=>x// x2 type is {}[]constx2=map(identity,[1,2,3]);
Expected behavior:
Return type of map function, when providing identity function should be number[]
Actual behavior:
Return type is {}[]
Also, I've realized that map function in 2nd example does not infers even T type. It's {} currently, while it's obvious should be a number type, as we are providing array of numbers:
The second interesting thing is, that type inferring works fine, if map function arguments will be reversed:
constmap=<T,U>(arr: T[],f: (x: T)=>U): U[]=>{returnarr.map((val)=>f(val));}// x2 type is number[]constx2=map1([1,2,3],identity);
This is an effect of TypeScript's type argument inference algorithm:
First, we process (from left to right) all arguments that are deemed context insensitive, which effectively means all arguments that don't contain function expressions with un-annotated parameters.
Then, we then separately process the context sensitive arguments (again from left to right), and for the un-annotated parameters in the contained function expressions, we fix inferences made for type parameters referenced in the corresponding contextual type and use the inferences we have made so far to compute and assign a type. Once a type parameter is fixed in this manner, we make no further inferences for that type parameter.
This differs from the unification based type inference implemented by some functional programming languages, but it has the distinct advantage of being able to make partial inferences in incomplete code which is hugely beneficial to statement completion in IDEs. For example, see #15680 (comment) and the thread in #17237.
Now, in your example we get it right when there is an arrow function argument because we classify that as contextually sensitive and process it after first making inferences from other arguments. But we can't handle the situation where the arguments don't appear context sensitive (which the simple identifier identity doesn't) and where we actually need to process the arguments in reverse order in order to succeed.
This would solve lots of other cases, like compose(), when they are given generic argument types, too. This is something that really hurts libraries like ramda and recompose.
Simon Buchan (@simonbuchan) Yes, but this type of unification gets exponentially more complicated when types aren't the simple naked type parameters in your example, but more complex constructs such as union and intersection types. For example, see #15016 (comment).
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
Activity
ahejlsberg commentedon Jul 30, 2017
This is an effect of TypeScript's type argument inference algorithm:
This differs from the unification based type inference implemented by some functional programming languages, but it has the distinct advantage of being able to make partial inferences in incomplete code which is hugely beneficial to statement completion in IDEs. For example, see #15680 (comment) and the thread in #17237.
Now, in your example we get it right when there is an arrow function argument because we classify that as contextually sensitive and process it after first making inferences from other arguments. But we can't handle the situation where the arguments don't appear context sensitive (which the simple identifier
identitydoesn't) and where we actually need to process the arguments in reverse order in order to succeed.simonbuchan commentedon Jul 31, 2017
Anders Hejlsberg (@ahejlsberg) Wouldn't lifting generic parameters in argument types work?
map(identity :: <T>(x: T) => T, [1, 2, 3] :: number[]) :: <T, U>(f: (x: T) => U, arr: T[]) => U[]map(identity :: (x: T_from_identity) => T_from_identity, [1, 2, 3] :: number[]) :: <T_from_identity>(f: (x: T_from_identity) => T_from_identity, arr: T_from_identity) => T_from_identity[]map(identity :: (x: number) => number, [1, 2, 3] :: number) :: (f: (x: number) => number, arr: number[]) => number[]This would solve lots of other cases, like
compose(), when they are given generic argument types, too. This is something that really hurts libraries likeramdaandrecompose.ahejlsberg commentedon Jul 31, 2017
Simon Buchan (@simonbuchan) Yes, but this type of unification gets exponentially more complicated when types aren't the simple naked type parameters in your example, but more complex constructs such as union and intersection types. For example, see #15016 (comment).
simonbuchan commentedon Jul 31, 2017
Following up this chain, I apologise for my "smart" suggestion raising old wounds :)
Looks like #16072 gets us nearly there, thanks for that!
mhegazy commentedon Aug 17, 2017
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.