You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Types for functions like Array.prototype.reduce or RxJS's "scan" seem impossible #46438
I searched, and couldn't find a related issue (but I find it very unlikely one doesn't exist). It seems like there should be something labeled "Design Limitation" or the like?
The basic problem is if you're using a standard "reducer" callback pattern with a seed that is different from what your reducer returns the returned type is incorrect.
// The type of result is `number`, but should be `string`.constresult=[1,2,3].reduce((acc,value)=>{// ~~~~~~~~~~~~~~~~~// ^-- Error Here: "No overload matches this call"if(acc===null){return''+value;}else{returnacc+', '+value;}},null);console.log(result);// "1, 2, 3"console.log(typeofresult);// "string"
🔎 Search Terms
is:issue is:open array reduce
is:issue is:open reduce callback
is:issue is:open reduce "No overload matches this call"
is:issue is:open reduce label:"Design Limitation"
🕗 Version & Regression Information
Any version of TS, to TMK. Specifically tested in 4.2 and 4.4.4
This is the behavior in every version I tried, and I reviewed the FAQ for entries as best I could. There's apparently a LOT of them. I didn't see anything related.
// The type of result is `number`, but should be `string`.constresult=[1,2,3].reduce((acc,value)=>{// ~~~~~~~~~~~~~~~~~// ^-- Error Here: "No overload matches this call"if(acc===null){return''+value;}else{returnacc+', '+value;}},null);console.log(result);// "1, 2, 3"console.log(typeofresult);// "string"
🙁 Actual behavior
A type error, even though the compiled JavaScript is valid.
🙂 Expected behavior
No type error, result should be a type string, not number.
Ben Lesh (@benlesh) there are a few mentions of reduce in #36554, but I would encourage you to copy this example over to there as well for more coverage. Thanks!
Our inference process is so fundamentally different here, but it does feel bad that when we have plain local statements, we're able to dive into the body of this loop and know it's either been set to string or null.
I think at some point, I would like to pick Anders Hejlsberg (@ahejlsberg)'s brain to understand how control flow analysis deals with these sorts of circularities (it's been a while for me, so I've largely forgotten). Maybe we could brainstorm on something that would work better with higher order functions like these. One issue with our inference process is that in our first pass going through arguments, we "lock in" inferences from expressions that aren't "contextually sensitive" (e.g. callbacks). The null is the only non-contextually sensitive thing here, so it wins. We could play around with tweaks to this process, but
it would be more complex
it might be a lot more work on every overload
it might break a huge chunk of code that exists today
Reacted by Andrew Branch, Toni Villena and Ben Lesh
Yeah. I'm familiar with the approach of Nicholai Main (@nmain). However, I would strongly advise people that they should leverage inference and not pass explicit generics. I say this because generics are just another element of an API signature that can change over major versions. So reduce((acc: null | string, item) => would have been my go-to instead of <null | string>. That means the generics are implicit and you don't have to worry if they happen to change in a release.
Honestly, Daniel Rosenwasser (@DanielRosenwasser), I would rather appreciate this specific issue remain open, because I feel that this issue is unique to a reduce-type callback situation. As we see it in RxJS's scan, which clearly has nothing to do with Array methods. It's just impossible to get inference working right here, and I've literally seen hundreds of examples of people shooting themselves in the foot with explicit types that weren't accurate to accommodate this.
Because in the example shown - there is nothing impossible here. Type error can be trivially fixed by the user:
replacing default value with a '' (empty string), instead of null - so all three types (accumulator, return, default value) would be the same, and the whole thing gets a nice signature reduce<A, T>((a: A, v: T) => A, default: A.
of course, resulting the need to rewrite the code in function body that depends on the null. This is not a problem for example you shown (`if(!acc)'), but could be trickier for more complicated real code. Still - the original problem is in using two types where there should have been one.
ps: i understand that many people would not agree on abandoning the use of null for default values, so those ones can type , null as null|string); at the end of their reduce/scan function call.
How can (and why should) Typescript promote null to a union type - that's another question, though.. reduce<A,T,D>(a:A|D, v:T) => A, default: A|D), looks pretty messy to me already, and if such promotion will be special-cased to null only (via conditional types), then it becomes almost magical (in a bad sense). Yet, if this will help people to describe their code more accurately - then maybe this is the way to go 🤷
Activity
nmain commentedon Oct 20, 2021
This is an inference problem, but not a typing problem: https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAeWoUKgAPqFHipAPgAU-gCGysrsAG5BhPjQAJSgOr6gAN6KIKAZmVnZObkZAH6FRcUlhWlgeZVV2QB6ALR1oACiWFioWKAAEszQAFygAEQAcqigqOHMhKhBgnRBcMowFHCQ5KDKUYQDilmkmMGhCTp6LoTxqQp5OHD4WNSgAOQPoADUoJHR0ADcO5kAvqBoIRcLALldoDc7qAQspXo92M83h8Yj9Lv9FH92KdYqiVGpcKhCNAZFNJP4cARiDjQOkBsZQGZQOZtgpVOpCcTSf5ECgMJpKXBqbTvBJJAMgA
andrewbranch commentedon Oct 20, 2021
I think this is covered by #36554. I usually handle this with something similar to Nicholai Main (@nmain)’s example, by adding an annotation on the accumulator. https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAKRwENlygFyhahQqAA+oKLiUuwAbi6E+NAAlKA6AHygAN6KoOlkmK7u8Tp6PoRxqQoZpZpw+FjUoADkNaAA1KARUdAA3GkZAL6g0IS4sMVlGTgVVaBuyo217PVNLdEdJd2KXewFMUsqariohNAyhKiSjjgExJugIKAARMagZqDmN9vqewdHJ4goGJrncJdrjdghJJDcgA
andrewbranch commentedon Oct 20, 2021
Ben Lesh (@benlesh) there are a few mentions of reduce in #36554, but I would encourage you to copy this example over to there as well for more coverage. Thanks!
DanielRosenwasser commentedon Oct 20, 2021
Our inference process is so fundamentally different here, but it does feel bad that when we have plain local statements, we're able to dive into the body of this loop and know it's either been set to
stringornull.I think at some point, I would like to pick Anders Hejlsberg (@ahejlsberg)'s brain to understand how control flow analysis deals with these sorts of circularities (it's been a while for me, so I've largely forgotten). Maybe we could brainstorm on something that would work better with higher order functions like these. One issue with our inference process is that in our first pass going through arguments, we "lock in" inferences from expressions that aren't "contextually sensitive" (e.g. callbacks). The
nullis the only non-contextually sensitive thing here, so it wins. We could play around with tweaks to this process, buttypescript-bot commentedon Oct 23, 2021
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
benlesh commentedon Oct 25, 2021
Yeah. I'm familiar with the approach of Nicholai Main (@nmain). However, I would strongly advise people that they should leverage inference and not pass explicit generics. I say this because generics are just another element of an API signature that can change over major versions. So
reduce((acc: null | string, item) =>would have been my go-to instead of<null | string>. That means the generics are implicit and you don't have to worry if they happen to change in a release.benlesh commentedon Oct 25, 2021
Honestly, Daniel Rosenwasser (@DanielRosenwasser), I would rather appreciate this specific issue remain open, because I feel that this issue is unique to a reduce-type callback situation. As we see it in RxJS's
scan, which clearly has nothing to do with Array methods. It's just impossible to get inference working right here, and I've literally seen hundreds of examples of people shooting themselves in the foot with explicit types that weren't accurate to accommodate this.c69-addepar commentedon Nov 2, 2021
Ben Lesh (@benlesh) are you asking for automatic union type promotion ?
Because in the example shown - there is nothing impossible here. Type error can be trivially fixed by the user:
''(empty string), instead ofnull- so all three types (accumulator, return, default value) would be the same, and the whole thing gets a nice signaturereduce<A, T>((a: A, v: T) => A, default: A.null. This is not a problem for example you shown (`if(!acc)'), but could be trickier for more complicated real code. Still - the original problem is in using two types where there should have been one.https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAKRwENly9gDcXhfNACUoDoAfKAA3oogoNExsXHxCdEAfimpaekpkWCJOblxAHoAtIWgAKJYWKhYoAASzNAAXKAARAByqKConsyEqC6CdC5wyjAUcJDkoMo+hM2KsaSYru5BOnq0hISBEQqJOHD4WNSgAOQnoADUoN6+0ADc8zEAvqDQhLiwO3vQB0egbspLqd2Ocrjc-A9ds9FE8QSd-JCVGpcKhCNAZL1JI4cARiAjQFFmsZQGZQOY5gpVOpUejMY5ECgMJpcXB8YTROIpM0gA
ps: i understand that many people would not agree on abandoning the use of null for default values, so those ones can type
, null as null|string);at the end of their reduce/scan function call.How can (and why should) Typescript promote
nullto a union type - that's another question, though..reduce<A,T,D>(a:A|D, v:T) => A, default: A|D), looks pretty messy to me already, and if such promotion will be special-cased to null only (via conditional types), then it becomes almost magical (in a bad sense). Yet, if this will help people to describe their code more accurately - then maybe this is the way to go 🤷