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

More smart type inference #1212

Closed
s-panferov opened this issue Nov 19, 2014 · 10 comments
Closed

More smart type inference #1212

s-panferov opened this issue Nov 19, 2014 · 10 comments
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript

Comments

@s-panferov
Copy link

I was trying to implement the Result type from Rust in TypeScript. Here is the result.

Everything works well but there is one thing that disappoints me:

static getApiToken(): Result<Token, AuthError> {
    return this.getCurrentUser().andThen(user => new Ok<Token, AuthError>(user.token));
}

In this code sample we can't just write new Ok(user.token) because TS can't infer AuthError type from function's return type. Do you have any plans to add this functionality? Is think that it is not even a breaking change.

@ahejlsberg
Copy link
Member

What is the exact type of the getCurrentUser method? The andThen method is declared to simply propagate the E of the Result instance it is invoked on.

@s-panferov
Copy link
Author

@ahejlsberg let's move to more simple example, just:

static getCurrentUser(): Result<CurrentUser, AuthError> {
    var currentUser: models.CurrentUser;
    return new Ok(currentUser)
}

Compiler gives me the error:

src/app/api/mod.ts(16,20): error TS2322: Type 'Ok<CurrentUser, {}>' is not assignable to type 'Result<CurrentUser, AuthError>'.
  Types of property 'map' are incompatible.
    Type '<U>(fn: (a: CurrentUser) => U) => Result<U, {}>' is not assignable to type '<U>(fn: (a: CurrentUser) => U) => Result<U, AuthError>'.
      Type 'Result<any, {}>' is not assignable to type 'Result<any, AuthError>'.
        Type '{}' is not assignable to type 'AuthError'.

It can infer AuthError type for second type argument from the function's return type and this is what i'm asking about.

@s-panferov
Copy link
Author

Saying more formally, I suggest this:

When we have some generic type with missed type arguments, we leave it as is until a first usage of this type in a context where these type arguments is known. Then we can substitute these types and use them as if they were pre-specified.

@ahejlsberg
Copy link
Member

If I understand the Result type correctly, it seems that the exception type E of one operation isn't really connected to the next operation. You define andThen like this:

interface Result<T, E> {
    andThen<U>(op: (x: T) => Result<U, E>): Result<U, E>;
    ...
}

which implies that the andThen operation has the same E as the result it is invoked on. But shouldn't it instead be:

interface Result<T, E> {
    andThen<U, F>(op: (x: T) => Result<U, F>): Result<U, F>;
    ...
}

You could then simply remove the return type annotation and rely on inference:

static getApiToken() {
    return this.getCurrentUser().andThen(user => new Ok<Token, AuthError>(user.token));
}

@DanielRosenwasser
Copy link
Member

@s-panferov I've left some feedback on your gist, have a look.

@s-panferov
Copy link
Author

@DanielRosenwasser thanks a lot! I will fix it!

@ahejlsberg Result and Option are the types that replace null pointers in Rust. They are very helpful in TS too for using instead of null.

You could then simply remove the return type annotation and rely on inference:

static getApiToken() {
return this.getCurrentUser().andThen(user => new Ok<Token, AuthError>(user.token));
}

You are right, I can do it the way you suggested, but there are several disadvantages:

  • I need to scan the whole function to find out the return type.
  • I can't return an interface, only the implementation (without specifying the function return type explicitly).
  • If I will have several exit points from my function, I will need to repeat these type annotations again and again. This is the major argument. Please take a look at the example:
// Without type inference
function sqrt(x: number): Result<number, string> {
    if (x < 0) {
        return new Err<number, string>("Negative square root")
    } else {
        return new Ok<number, string>(Math.sqrt(x))
    }
}

// With type inference
function sqrt(x: number): Result<number, string> {
    if (x < 0) {
        return new Err("Negative square root")
    } else {
        return new Ok(Math.sqrt(x))
    }
}

@s-panferov
Copy link
Author

There are a lot of other cases where one can use this kind of type inference, e.g.

declare function request<T>(url: string, options: any): Promise<T>;

/*
 * Without inference
 */

 // All is clear and readable, but we doubling the type annotations.
function loadCurrentUser(): Promise<CurrentUserReply> {
    return request<CurrentUserReply>('/api/user/current', { /* .. */ })
}

 // I have to remember the `request`'s return type all the time.
function loadCurrentUser() {
    return request<CurrentUserReply>('/api/user/current', { /* .. */ })
}

/*
 * With inference
 */

// The return type is clear and there are no doubling.
function loadCurrentUser(): Promise<CurrentUserReply> {
    return request('/api/user/current', { /* .. */ })
}

@s-panferov
Copy link
Author

I have one more example with Maybe/Option type that shows that usage of generics will be simpler:

function sayHello(name: Option<string>) {
  if (name.isSome()) {
    alert("Hello " + name.unwrap()) 
  } else {
    alert("Hello, Anonymous")
  }
}

// I can't write this way
sayHello(new None());
// error TS2345: Argument of type 'None<{}>' 
// is not assignable to parameter of type 'Option<string>'.

// I have to to write this:
sayHello(new None<string>());

There can be a very complex type (maybe generic) instead of string and I have to repeat it again and again in my code. I understand that this feature is not high-priority for TS, but it will be nice to see it in future versions.

@danquirk danquirk added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Nov 25, 2014
@hmaurer
Copy link

hmaurer commented Dec 14, 2016

Has there been any work done on this? It would be extremely useful to have this kind of inference as part of the language; I stumbled upon this exact problem tonight.

@mhegazy
Copy link
Contributor

mhegazy commented Dec 14, 2016

Looking at this again, this is the same as #11152.

@mhegazy mhegazy added Duplicate An existing issue was already created and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Dec 14, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants