Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array Reduce Typing Error #15071

Closed
forbesmyester opened this issue Apr 7, 2017 · 4 comments
Closed

Array Reduce Typing Error #15071

forbesmyester opened this issue Apr 7, 2017 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@forbesmyester
Copy link

TypeScript Version: 2.1.6

Code

        function reducer(acc: [number, number], d): [number, number] {
            return acc;
        }

        let input = [
            ["Name", "Age", "Sex", "Hair Colour", "Home Town"],
            ["Nancy", 31, "F", "Brown", "Plymouth"],
            ["Fred", 31, "M", "Ginger", "Edinburgh"],
            ["Cindy", 31, "F", "Blonde", "Birmingham"],
            ["Al", 31, "M", "Brown", "Sheffield"]
        ];


        let initial: [number, number] = [0, -1];
        let r: [number, number] = input.reduce(reducer, initial);

Expected behavior:

It should compile.

I cannot see how I can be more specific with these types without changing initial to an object, for example.

Actual behavior:

It seems to get confused thinking the result of input.reduce would be (string | number)[] where it would should be [number, number].

The error message is:

src/src.ts (73,13): error TS2322: Type '(string | number)[]' is not assignable to type '[number, number]'. Property '0' is missing in type '(string | number)[]'.

@MrAndersen1
Copy link

When types of "acc" and "d", in this case, differs you can write it as
let r: [number, number] = input.reduce<[number, number]>(reducer, initial);
That being said I'm not sure there's no bug here or even that this is the way you should type it.

@forbesmyester
Copy link
Author

Aha, thanks for that, your suggestion does seem to work. I do think I gave it enough information to figure out what I intended though.

Changing the acc / initial types to type LowHigh = { low: number, high: number } did fix it but a tuple of 2 numbers is a perfectly reasonable way of representing this (in code which is internal to a specific function)...

@ahejlsberg
Copy link
Member

The issue here is that the type [number, number] is assignable to the type (string | number)[], so overload resolution can't tell that you intend to use the version of reduce where the accumulator type differs from the array element type. As @MrAndersen1 suggested, you can explicitly choose that overload by specifying the type argument.

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 7, 2017
@forbesmyester
Copy link
Author

Firstly I want to thank both of you for your time and responses.

I understood that [number, number] can be assigned to (string | number)[] before I submitted the issue and that it was the fact that they are somewhat compatible which was probably the cause of my perceived issue.

I am still have an uneasy feeling with this because I feel that a reduce() implementation which returns a type different from that which is supplied in initialValue is something very strange and would not be possible in at least some (or perhaps all) of the typed languages I have used.

Looking at src/lib/es5.d.ts there are two definitions for reduce() I think TS picked the following definition:

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;

Whereas I feel the other definition would be more appropriate:

reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;

This may very well be naive, but it seems that if the initialValue parameter is supplied, the former definition can be satisfied by the latter. If that is the case I do not understand the reason for the former having the optional last argument instead of it just being absent? If that were done would TS not always use the latter when initialValue is supplied and the former when it is not, this would seem correct to me... Perhaps there are usages of reduce that I am not considering?

This seems to have quite a lot in common with #7014 though I note that is to do with swapping the type definitions around, I am not sure if that would be another possible way to solve my perceived issue as well.

I have made the change locally in src/lib/es5.d.ts and the tests do fail. I appreciate might mean that my thinking is completely incorrect, but it is not at all clear to me the reason why from looking at where it failed ( here and here ).

@mhegazy mhegazy closed this as completed Apr 24, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants