Typescript: Supporting generic type inference over the other higher-order functions

Created on 27 Jun 2016  路  14Comments  路  Source: microsoft/TypeScript

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);
Difficult Fixed Needs Proposal Suggestion

Most helpful comment

Higher order function type inference now implemented in #30215.

All 14 comments

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.

10247, #9949 for the reference. And see @gcnew's comment

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.

Was this page helpful?
0 / 5 - 0 ratings