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
.