An overloaded function has a set of call signature declarations which determine how the function can be called, and (assuming the function is implemented and not just declared) a single implementation. The implementation signature is not callable.
In your example code, you have a single call signature
// call signature
function scan<A>(this: A[], f: (a: A, x: A) => A): A[];
and an implementation
// implementation
function scan<A, B>(this: A[], f: (a: B, x: A) => B, init?: B): B[] {
/* snip */
}
But this does not seem to be what you intend. You really want those signatures both to be call signatures, like this:
// call signatutes
function scan<A>(this: A[], f: (a: A, x: A) => A): A[];
function scan<A, B>(this: A[], f: (a: B, x: A) => B, init?: B): B[];
// implementation
function scan(...) {
So the question is: what should the implementation signature be?
TypeScript's compiler is not able to check the implementation by checking it against each call signature separately. There was a suggestion to do this at microsoft/TypeScript#13235, but it was closed as too complex to implement. Instead, what the compiler does is make sure that the implementation signature parameters can handle the parameters from each of the call signatures, and make sure that the implementation signature return type can handle the return return types from each of the call signatures. That is, the return type can be the union of all the call signatures' return types. This is not type safe (because you might be returning the wrong type for a particular call signature), but convenient.
For better or worse, such loose checking is the way TypeScript's overload implementation works. So you need to be careful when you write overloaded functions.
Anyway, that means the implementation needs to be something like this:
// implementation signature
function scan<A, B>(this: A[], f: (a: B | A, x: A) => A, init?: B | A) {
if (init === undefined) {
const result = [this[0]];
for (let i = 1; i < this.length; i++) {
result.push(f(result[i - 1], this[i]));
}
return result;
}
const result = [init];
for (let i = 0; i < this.length; i++) {
result.push(f(result[i], this[i]));
}
return result;
}
It's not perfect by any means, but it's probably the best we can get if we want to put these two separate behaviors into a single overloaded function.
Playground link to code