Consider the following snippet:
type Add = (a: number | string, b: number | string) => number | string;
function hof(cb: Add) {}
const addNum = (a: number, b: number): number => a b;
const addStr = (a: string, b: string): string => a b;
// @ts-expect-error
hof(addNum);
// @ts-expect-error
hof(addStr);
Why can't we pass addNum
and addStr
functions to hof
, IMO their call signatures should be compatible with what hof
expects, but it actually isn't.
And if their types are incompatible, then why doesn't the overload signatures in the following snippet complain?
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number | string, b: number | string): number | string {
return 1;
}
CodePudding user response:
You are assuming covariant types, but function arguments are contravariant. (see
How can the return type be used?
Now let's see the ways in which hof
can use the value returned by cb
.
Because cb
can return either a number
or a string
, it's hof
's responsibility to use the return value wisely, meaning that hof
should not assume that the returned value is always a number or a string, hof
needs to put appropriate type guards before making any such assumptions.
function hof(cb: (a: number | string) => number | string) {
// @ts-expect-error
const fixed = cb(100).toFixed() // wrong assumption that the returned value is always a number
}
function hof(cb: (a: number | string) => number | string) {
const ret = cb(100)
if (typeof ret === "number") { // appropriate check to make sure that it's a number
const fixed = ret.toFixed();
}
}
And again notice the place where the error is thrown, it's thrown inside the function body indicating that there's a problem with the implementation of the function.
The callback passed to hof
can also just return a number
and TS would be absolutely fine with that.
function hof(cb: (a: number | string) => number | string) {}
function double(a: number | string): number {
return 2 * Number(a)
}
hof(double)
So, the callbacks passed to hof
should only return a subset of values that hof
expects the callback to return.
Overload Signatures
The behavior with overload signatures is different, the purpose of overloading is to restrict the behavior of a function.
So, arguments and return values of overload signatures should always be a subset of the arguments and return values of the implementation signature.