Currently, TypeScript infers generic types when referring its type immediately. Its behavior is undesirable for using higher-order functions. The type inference of generics should delay until it will be called.
function flip<a, b, c>(f: (a: a, b: b) => c): (b: b, a: a) => c {
return (b: b, a: a) => f(a, b);
}
function zip<T, U>(x: T, y: U): [T, U] {
return [x, y];
}
var expected: <T, U>(y: U, x: T) => [T, U] = flip(zip);
var actual: (y: {}, x: {}) => [{}, {}] = flip(zip);
this issue is really problematic for functional paradigm users :
const map = <T, U>(transform: (t: T) => U) =>
(arr: T[]) => arr.map(transform)
const identity = <T>(t: T) => t;
const identityStr = (t: string) => t;
const arr: string[] = map(identityStr)(['a']);
const arr1: string[] = map(identity)(['a']); // Type '{}[]' is not assignable to type 'string[]'.
(welcome back @fdecampredon!)
(thanks ! :) )
Yep, there is to many <any>
in my Ramda code.
Relevant paper: "Practical type inference for higher-rank types". Specifically the section on subsumption of parametrically polymorphic signatures.
Ultimately this comes down to better unification, which is what @gcnew has done some work on if you follow along in #9949. For the flip
example above, the rank 1 function type <T, U>(x: T, y: U) => [T, U]
can be substituted for the rank 0 function type (a: a) => (b: b) => c
to obtain f: (x: a, y: b) => [a, b]
, because the former is more polymorphic than the latter. From there we need TypeScript's usual approach to unification to kick in and infer c
= [a, b]
. The tricky part is coming up with a general algorithm to do all of this.
Another example:
const compose = <A, B, C>(g: (b: B) => C, f: (a: A) => B) => (a: A) => g(f(a))
const identity = <T>(x: T) => x
const lessThanTen = (x: number) => x < 10
const composed = compose(lessThanTen, identity)
// ^^^^^^^^^^^
// Argument of type '(x: number) => boolean' is not assignable to parameter of type '(b: {}) => boolean'.
// Types of parameters 'x' and 'b' are incompatible.
// Type '{}' is not assignable to type 'number'.
Can be fixed with explicit type parameters
const composed = compose<number, number, boolean>(lessThanTen, identity)
but would be really nice if this could just unify!
I would love to know if there is some movement here. Here's another usecase: https://stackoverflow.com/q/47934804/592641
I've ran into this issue when using lodash's memoize
function when the function being memoised is generic.
This would also be really valuable for mixins on react components with proptypes
export class MyComponent extends MyMixin(React.Component<MyComponentProps>) {}
I _love_ the new mixins feature since 2.2 and I was really excited about refactoring my code to create a variety of mixins for my components until I realized as of right now it won't quite work as I'd hoped without a lot of manual type declaring 馃槶
Heads up I put up my attempt at implementing this at #24626. No idea if it'll go anywhere but it works for a lot of the cases I've tried so feel free to try it out if you're interested.
@kpdonn 馃槶 It's so beautiful.
Any progress on this?
Higher order function type inference now implemented in #30215.
Most helpful comment
Higher order function type inference now implemented in #30215.