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)[]'.
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.
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)...
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.
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 ).
Most helpful comment
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.