Home > Net >  Can't apply reduce on array of numbers or nulls
Can't apply reduce on array of numbers or nulls

Time:09-28

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.

  • Related