I was playing with TS, here I just wanted a method that would sum numbers (assuming some elements could be null too, so those would be ignored):
let squareNumbers = (numbers: (number | null )[]): number => {
return numbers.reduce((accumulator,c)=> {
return c === null ? accumulator : accumulator c
}, 0);
}
console.log(squareNumbers([1,2, null, 4,5]))
Expected output: 12
But I get error:
Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'
Not sure about what it is complaining? I thought I had some checks for that like c === null and using 0 as accumulator's initial value.
Update: Ok I think that complain was about the type of what I was returning from main function, after changing above code to this:
let squareNumbers = (numbers: (number | null )[]) => { // removed return type
return numbers.reduce((accumulator,c)=> {
return c === null ? accumulator : accumulator c
}, 0) ;
}
Now it complains accumulator can be null. But shouldn't it deduce that it can't be null in my example?
CodePudding user response:
Typescript defines arrays as
interface Array<T> {
...
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U, initialValue: U): U;
}
In your code, the numbers array (numbers: (number | null )[]) is Array<number | null> and T is number | null.
Typescript needs to check which overload of reduce you mean. It probably tries the first definition first and will see that there is no type violation. All parameters are number | null, or subtypes thereof, namely null or number or the type 0 which is a subtype of number.
So it selects
type T = number | null
reduce(cb: (accumulator: T, c: T) => T, initial: T): T
and then complains that you can't do accumulator c because accumulator could be null.
What you want to use in your code is the other overload though that maps from the array's type T to any other type U during the reduce operation.
By calling it with
numbers.reduce((accumulator: number,
you create a case where the callback no longer accepts number | null in accumulator, so that overload is no longer valid. Instead it tries the other one and would find that
type T = number | null
type U = number
reduce(cb: (accumulator: U, c: T) => U, initial: U) => U
works without violation.
The other alternative is to explicitly define U by calling it as
reduce<number>((accumulator, c) => ..., 0)
The switch between those overloads usually works without specifying the type, but in this case you have types that happen to be too compatible to each other.
CodePudding user response:
You need to provide type for the reduce callback as well
let squareNumbers = (numbers: (number | null)[]): number => {
return numbers.reduce((accumulator: number, c: number | null) => {
return c === null ? accumulator : accumulator c
}, 0);
}
You can always do something like this in the callback
let squareNumbers = (numbers: (number | null )[]) => { // removed return type
return numbers.reduce((accumulator, c)=> {
if (accumulator === 2) {
return null;
}
return c === null ? accumulator : accumulator c
}, 0) ;
}
That's why the compiler is complaining that accumulator could be null.
