Skip to content

More smart type inference #1212

Closed
Closed

Description

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.

Activity

  1. ahejlsberg commented on Nov 19, 2014

    @ahejlsberg
    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.

  2. s-panferov commented on Nov 19, 2014

    @s-panferov
    Author

    Anders Hejlsberg (@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.

  3. s-panferov commented on Nov 19, 2014

    @s-panferov
    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.

  4. ahejlsberg commented on Nov 19, 2014

    @ahejlsberg
    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));
    }
  5. DanielRosenwasser commented on Nov 19, 2014

    @DanielRosenwasser
    Member

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

  6. s-panferov commented on Nov 19, 2014

    @s-panferov
    Author

    Daniel Rosenwasser (@DanielRosenwasser) thanks a lot! I will fix it!

    Anders Hejlsberg (@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))
        }
    }
  7. s-panferov commented on Nov 19, 2014

    @s-panferov
    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', { /* .. */ })
    }
  8. s-panferov commented on Nov 25, 2014

    @s-panferov
    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.

  9. added
    SuggestionAn idea for TypeScript
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Nov 25, 2014
  10. hmaurer commented on Dec 14, 2016

    @hmaurer

    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.

  11. mhegazy commented on Dec 14, 2016

    @mhegazy
    Contributor

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

  12. added
    DuplicateAn existing issue was already created
    and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Dec 14, 2016
  13. locked and limited conversation to collaborators on Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already createdSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions