Hacker News new | ask | show | jobs
by fluffything 2475 days ago
IIRC there are a couple of Rust write ups about this.

In Rust, inference is limited within functions. The language doesn't allow inferring the argument or return types of functions to avoid this kind of action at a distance.

The function API is just one of the many arbitrary places where one can limit inference and require users to provide types. In other languages the module boundary might also be an appropriate place to do that.

4 comments

A big part of TS is the ease of transition from JS where you immediately get benefits through gradual typing and return type inference is probably a big part of that.
This is true, but a lot of projects are now starting with TS, and maybe could benefit from intentional limitations on inference, at least locally. I’m pretty sure most folks start new projects with strict enabled as is.
In my experience a lot of people don’t necessarily start projects with strict mode — and they _really really_ should ... most people I’ve talked to who couldn’t figure out how to use typescript never figured things out because they ended up somehow with a compiler configured with noImplicitAny: false — typescript really needs to find a way to change the default here ...
Yes, my advice is almost always strict should be true for every new project, and even sometimes for migration projects where the end goal is to absolutely decrease tech debt and bugs as quickly as possible and the team isn't afraid of a giant compiler error waterfall as motivation. (It's a nice auto-generated TODO list with some semblance of progress reporting!) But at the very least, everyone should do themselves the favor and "noImplicitAny: true" no matter if it's new, migration, something inbetween, and no matter what they think of any of the other strict flags. Explicit anys are searchable TODO markers and fine, but implicit anys always seem to be hidden bugs waiting to be found.
Sure. But if you write some code (or update a dependency) and it breaks with spooky action at a distance, the first thing you should do is to pay off your tech debt and add type signatures to find the breakage, before thinking about changing TS.

The github comment says: " (I might suggest the underlying problem in this code is relying on inference too much, but the threshold for "too much" is difficult to communicate to users.)"

The first part is exactly right; the second part is well established in the Rust language spec and Haskell style guides like http://www.cis.upenn.edu/~cis552/current/styleguide.html and https://kowainik.github.io/posts/2019-02-06-style-guide#func...

The compiler should be capable of inferring the type as far as reasonably possible. But it should be an error to not specific the types of top level functions and class methods.

This way, when you write

    const f = (a, b) => a + b
the compiler can tell you to write:

    const f => <T extends number|string>(a: T, b: T): T => a + b
or

    const f : <T extends number|string>(a: T, b: T) => T
            = (a, b) => a + b
This would be effectively like GHC's typeholes, where the compiler will tell you what to write when you say

    f :: _
    f a b = a <> b
except that rather than being an optional step by a developer, it's a mandatory standard.

(Well, I don't think typescript infers that type for that function, but whatever it actually infers, that's what should be there in the error message.)

I don't think typeholes is necessary for that feature. It suffices to just use type check up to the function signature. If I write:

    fn foo() { 42 } 
in Rust, I get an error that says that `i32` (the type of `42`) is not of type `()` (unit), which is the return type of `foo`. So I can just change it to:

    fn foo() -> i32 { 42 } 
instead. The same applies for generics, if I write:

    fn bar<T>(x: T, y: T) -> T { x + y }
I get the precise error that T does not implement the `Add<T>` trait. In this case the error is precise because there is only a trait in scope that supports `+`, but once there are many traits that would fit, which is rare, the compiler suggests some (often all of them). With that error I can just change that to:

    fn bar<T: Add>(x: T, y: T) -> T { x + y }
This is all done through local type inference within the function.
Same choice Java made.
This sounds like a good tradeoff. Perhaps this could be added to future versions of TS with a flag, if it's not already in there.