Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parameters: presume generics over any #14078

Closed
KiaraGrouwstra opened this issue Feb 15, 2017 · 5 comments
Closed

parameters: presume generics over any #14078

KiaraGrouwstra opened this issue Feb 15, 2017 · 5 comments
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@KiaraGrouwstra
Copy link
Contributor

TypeScript Version: any

Code

let identity = (x) => x; // degenerates to `(x: any) => any` instead of <T>(x: T) => T
let wrong: number = identity('foo'); // type-checks, while TS should have enough info to infer it's wrong

Expected behavior:
TS should be able to save parameter types (where unspecified) into generics so as to improve inference.

Actual behavior:
Things type-check while TS should have enough info to conclude they shouldn't.

Perhaps this could make for a compiler flag?

@masaeedu
Copy link
Contributor

This is similar to what F# does (where it is known as automatic generalization).

@RyanCavanaugh RyanCavanaugh added Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript labels Apr 13, 2017
@RyanCavanaugh
Copy link
Member

Assuming this isn't just "Recognize the identity function", this needs a description of how it might plausibly work

@masaeedu
Copy link
Contributor

masaeedu commented Apr 13, 2017

@RyanCavanaugh The goal is to ensure that the types of function arguments (which will be available at the call site) can participate in inference of the return type. Here's a rough gist of the idea.

Contextual type inference for functions needs to change so that the type inferred for result:

function f(a, b, c) {
    if (10 > 11) {
        return a;
    }
    if (192 - 7 < 11) {
        return b;
    }
    return c;
}

const { s, n, d } = { s: "Hello", n: 10, d: new Date() };
const result = f(s, n, d); // any

Is identical to what is inferred in the following snippet:

function f<T1, T2, T3>(a: T1, b: T2, c: T3) {
    if (10 > 11) {
        return a;
    }
    if (192 - 7 < 11) {
        return b;
    }
    return c;
}

const { s, n, d } = { s: "Hello", n: 10, d: new Date() };
const result = f(s, n, d); // string | number | Date

As a starting point, let's say we simply desugar all function declarations so that every parameter without an explicit type annotation corresponds to a generic type parameter in the final type signature of the function.

// The following:
function foo(a: number, b: string, c) { return c; }

// Is now equivalent from a type system perspective to:
function foo<T1>(a: number, b: string, c: T1) { return c; }

When called without type arguments, this behaves the same as any inference for c. When called with an argument of a known type in the c position (or an explicit type argument), you will get a more accurate return type instead of any.

The tricky part is figuring out how this interacts with explicitly generic functions. Because we don't have HKTs, and type signatures can't be partially applied, it is hard to figure out what type signature should be constructed for this function:

function prop<V>(object, key): V { /* ... */ }

Do the inferred generic arguments simply get appended to the function's type signature? Because you can't partially supply generic type arguments, this results in incovenience at the call site:

// If the type system treats the above as:
function prop<V, T1, T2>(object: T1, key: T2): V { /* ... */ }

// Calling the function becomes annoying:
const val1 = foo({ h: "ello" }, "h"); // Inferred as any
const val2 = foo<string>({ h: "ello" }, "h"); // Can't do this, passing type args is all-or-nothing
const val3 = foo<string, {}, string>({ h: "ello" }, "h"); // You have to pass mysterious type arguments you never explicitly declared

Some possible solutions:

  • Limit the feature to those functions that don't already have explicit type parameters (such as identity)
  • Allow parametrized type signatures to be applied partially (I know there's an issue for that somewhere)
  • Trace the additional type parameters internally and don't surface them in the function's type signature. They need to show up in the return type at the call site somehow, so this probably doesn't make sense, or would require introducing major conceptual changes like "variable type parameters" or something

Don't really have any great ideas on this front. I'm also probably missing a bajillion edge cases and interactions with other language features, but that's the rough idea.

Some other thoughts:

  • An important consideration here is that this hypothetical implicitGenerics flag would not play well with noImplicitAny, since the type parameters being generated are unconstrained
  • This is a very general way to represent function signatures, and in fact even parameters with explicit annotations can simply be represented as constraints on a type parameter (e.g. function foo(s: string) { ... } is equivalent to function foo<T1 extends string>(s: T1) { ... }
  • Something like this would allow for a great deal of extensibility in other areas of inference in the future, since you can express things like Type Inference on function arguments #15114 as constraints on the generated type parameters

@KiaraGrouwstra
Copy link
Contributor Author

Thanks for helping elaborate, @masaeedu. :)

To add to your post a bit:

we don't have HKTs, and type signatures can't be partially applied

Allow parametrized type signatures to be applied partially (I know there's an issue for that somewhere)

Partial application of generics sounds like #14400, while HKTs seemed the topic of #1213.

@KiaraGrouwstra
Copy link
Contributor Author

Closing in favor of #17428.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants