So, imagine I have a (dumb) function like this:
function doSomething(input: number|string): boolean {
if (input === 42 || input === '42') {
return true;
} else {
return false;
}
}
Why am I allowed to call array.some()
like this:
function doSomethingWithArray(input: number[]|string[]): boolean {
return input.some(i => doSomething(i));
}
But not array.every()
like this:
function doEverythingWithArray(input: number[]|string[]): boolean {
return input.every(i => doSomething(i));
}
Which gives me this error:
This expression is not callable. Each member of the union type '{ (predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; } | { ...; }' has signatures, but none of those signatures are compatible with each other.
I don't understand the difference. In my mind either both should work, or neither. What am I missing?
Note that doSomething()
accepts number|string
as its argument, so it should work with every element of number[]|array[]
, like it does for array.some()
, shouldn't it?
CodePudding user response:
That has to do with the definition of every()
every<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): this is readonly S[];
In a case of your union the signature become's
{
<S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): true;
(predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): this is S[];
} |
{
<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): true;
(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): this is S[];
}
You can see that the predicates have incompatible types.
CodePudding user response:
As we already discussed in the comments, every
has an overload containing a generic function with a type predicate while some
has no such things.
Why are the function signatures "incompatible" now?
- Its definitely related to the fact that they are generic. I have yet to find a definitive answer. Maybe @jcalz knows a satisfying explanation :)
Why is every
generic and has a type predicate?
Contrary to
some
, callingevery
can have an effect on the type of the array. If you have an array likeconst arr: (string | number)[] = []
Calling
every
on it and checking if every element is anumber
should also change the type of the resulting array.const arr2 = arr.every((e): e is number => typeof e === "number") // arr2 should be number[]
This is only possible to achieve with type predicates. And to allow type predicates to have an effect here,
every
must be generic. (Otherwise the type predicate would be ignored byevery
)
So what can you do about it?
I would suggest changing the type of input
to an intersection of (number[] | string[])
and (number | string)[]
. This may look weird but it does fix the error here.
function doEverythingWithArray(
input: (number[] | string[]) & (number | string)[]
): boolean {
return input.every(i => doSomething(i));
}
The function also remains callable as before.
// works as before
doEverythingWithArray(["a", "b", "c"])
doEverythingWithArray(["a", "b", "c"] as string[])
doEverythingWithArray([0, 1, 2])
doEverythingWithArray([0, 1, 2] as number[])
// fails
doEverythingWithArray([0, 1, "a"])