Most of the time I read that we should use type inference as much as possible. When writing a function I understand we must type arguments since they cannot be inferred, but why do we have to type the return value? TypeScript is taking care of that. What are the benefits of explicitly typing the return value of a function? So far I only read that I should do it, but no one is saying why.
The compiler can infer what your code does, but it has no idea what you intended. Take this trivial example:
function thing(value: string) {
return value === "foo" ? 123 : "456";
}
The inferred type is function thing(value: string): 123 | "456"
, and that matches the implementation, but is that in any meaningful sense right? Perhaps I intended to always return a number
, for example; if I tell the compiler that, it can tell me I didn't:
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.(2322)
Particularly when you're using complex wrapper/generic types (e.g. I see issues around this a lot in angular where RxJS observables are being used), this can really help getting early feedback on your assumptions.
I also work a lot with test-driven development (TDD), where one of the values of writing tests before implementation is that it gives you the opportunity to discuss the interface at a time where the cost of changing it is close to zero. Using the classic RPS example:
function rps(left: string, right: string) {
return "right";
}
it("returns 'right' for 'rock' vs. 'paper'", () => {
expect(rps("rock", "paper")).to.equal("right");
});
That'll compile and pass, but is it what we want? Now we can talk about options:
Do we accept the inferred type function rps(left: string, right: string): string
?
Go more specific with e.g.
type Throw = "rock" | "paper" | "scissors";
type Outcome = "left" | "right" | "draw";
function rps(left: Throw, right: Throw): Outcome { ... }
Use enums instead?
We can talk through the trade-offs and pick the best option given what we know at the time. The explicit return type serves as documentation of the decision we made.
I'd recommend making @typescript-eslint/explicit-function-return-type
an error, if you're linting your code, which provides as its rationale:
...explicit return types do make it visually more clear what type is returned by a function. They can also speed up TypeScript type checking performance in large codebases with many large functions.