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
flatMap errors when callback returns certain union types #31033
Argument of type '(key: any) => number[] | undefined' is not assignable to parameter of type '(this: undefined, value: string, index: number, array: string[]) => number | readonly number[]'.
Type 'number[] | undefined' is not assignable to type 'number | readonly number[]'.
Type 'undefined' is not assignable to type 'number | readonly number[]'. ts(2345)
Turning strict null checks off prevents the error as you might expect, but that's obviously not desirable.
More generally, any callback that returns foo[] | bar (where bar is not compatible with foo) seems to run into the same issue. The Map.prototype.get example I've used here just seems like it would be a relatively common use case in particular (you'd typically follow it up by calling .filter(Boolean) or similar on the mapped result).
More code
Seeing if doing the operations separately will help:
It's not a bug. The signature correctly models the standard library behavior. flatMap expects a function that returns a type => U | readonly U[] (some type or an array of that type). Your function returns => undefined | number[]. undefined doesn't match with number. You need to either provide a default value (=> myMap.get(key) || [] which works like .filter(Boolean)) or explicitly ignore the null check (=> myMap.get(key)!).
The signature correctly models the standard library behavior.
It models a certain statically-typed interpretation of the behaviour, but there's nothing in the ES spec that prevents it. All flat/flatMap cares about is whether each element is an array (in which case it unwraps it), or not (in which case it leaves it alone), it doesn't say that the elements in the arrays being unwrapped must be the same type as the non-wrapped elements at the top level.
Here's another, more contrived, example that currently errors in TS (but works fine in JS):
Correct. The callback could return (?U)[] and the function ?U|U[], or they could both be any, or you could disable strict. The problem is that contrived examples would hurt type inference for well-reasoned code. Then TypeScript would be less useful in general.
you'd typically follow it up by calling .filter(Boolean)
=> myMap.get(key) || [] does this in a single pass without introducing unwanted undefineds. It's a general pattern that works for any type (monad) with that method (also called chain). There's a super helpful #typescript channel on the FP Slack
The problem is that contrived examples would hurt type inference for well-reasoned code.
Can you show an example using my solution above where that would be the case? If there is value in being this inflexible I'd like to know where it lies.
Even so, I had thought TypeScript was more descriptive than prescriptive.
=> myMap.get(key) || [] does this in a single pass without introducing unwanted undefineds.
Right, I'm not discounting that pattern, it's just that there is also a more general case here.
changed the title [-]flatMap errors when callback returns certain union type[/-][+]flatMap errors when callback returns certain union types[/+]on Apr 19, 2019
I started to write an answer, but the bug tracker seems like an inappropriate forum. It works as designed. I encourage you to ask on Stack or Slack. You can @ me on the previously mentioned Slack channel.
I think the normal use case for flatMap is producing a homogeneous array and the current definition reflects that. To that point, absent other information, the example you posted looks like it has a bug, because probably the author meant to filter out unfound values rather than dropping undefineds into the resulting array. Writing flatMap<number | undefined>( to indicate your intent seems like an increase in clarity.
Reacted by Malcolm, uhyo, Trotyl Yu, Mendy Berger, BoyeTRev and erikfunkis
Writing flatMap<number | undefined>( to indicate your intent seems like an increase in clarity.
I agree - thanks for the tip, I didn't realize that would work.
I still think there probably exists other "valid" use cases for a heterogeneously mapped array but admit I can't think of anything particularly compelling right now, and such cases could just do the same as above.
However it does seem a little inconsistent then that plain map has no problem with any union type given your position here. Many people's mental model for flatMap is that it is simply map with the added option of producing multiple elements for a given input element. More to the point, if the normal use case for flatMap is producing a homogeneous array, then surely that applies to map (and I guess by extension, non-tuple arrays in general) too? Is that discrepancy just due to being a technical simplicity/elegance thing?
Reacted by Mendy Berger, Ivan Kleshnin and Clarity
Activity
noinkling commentedon Apr 19, 2019
After playing around with the definition, this change using conditional types seems to make
flatMapbehave how I want:texastoland commentedon Apr 19, 2019
It's not a bug. The signature correctly models the standard library behavior.
flatMapexpects a function that returns a type=> U | readonly U[](some type or an array of that type). Your function returns=> undefined | number[].undefineddoesn't match withnumber. You need to either provide a default value (=> myMap.get(key) || []which works like.filter(Boolean)) or explicitly ignore the null check (=> myMap.get(key)!).noinkling commentedon Apr 19, 2019
It models a certain statically-typed interpretation of the behaviour, but there's nothing in the ES spec that prevents it. All flat/flatMap cares about is whether each element is an array (in which case it unwraps it), or not (in which case it leaves it alone), it doesn't say that the elements in the arrays being unwrapped must be the same type as the non-wrapped elements at the top level.
Here's another, more contrived, example that currently errors in TS (but works fine in JS):
texastoland commentedon Apr 19, 2019
Correct. The callback could return
(?U)[]and the function?U|U[], or they could both beany, or you could disablestrict. The problem is that contrived examples would hurt type inference for well-reasoned code. Then TypeScript would be less useful in general.=> myMap.get(key) || []does this in a single pass without introducing unwantedundefineds. It's a general pattern that works for any type (monad) with that method (also calledchain). There's a super helpful#typescriptchannel on the FP Slacknoinkling commentedon Apr 19, 2019
Can you show an example using my solution above where that would be the case? If there is value in being this inflexible I'd like to know where it lies.
Even so, I had thought TypeScript was more descriptive than prescriptive.
Right, I'm not discounting that pattern, it's just that there is also a more general case here.
[-]flatMap errors when callback returns certain union type[/-][+]flatMap errors when callback returns certain union types[/+]texastoland commentedon Apr 19, 2019
I started to write an answer, but the bug tracker seems like an inappropriate forum. It works as designed. I encourage you to ask on Stack or Slack. You can
@me on the previously mentioned Slack channel.RyanCavanaugh commentedon Apr 19, 2019
The ES spec prevents almost nothing; see https://stackoverflow.com/questions/41750390/what-does-all-legal-javascript-is-legal-typescript-mean
I think the normal use case for
flatMapis producing a homogeneous array and the current definition reflects that. To that point, absent other information, the example you posted looks like it has a bug, because probably the author meant to filter out unfound values rather than droppingundefineds into the resulting array. WritingflatMap<number | undefined>(to indicate your intent seems like an increase in clarity.noinkling commentedon Apr 20, 2019
I agree - thanks for the tip, I didn't realize that would work.
I still think there probably exists other "valid" use cases for a heterogeneously mapped array but admit I can't think of anything particularly compelling right now, and such cases could just do the same as above.
However it does seem a little inconsistent then that plain
maphas no problem with any union type given your position here. Many people's mental model forflatMapis that it is simplymapwith the added option of producing multiple elements for a given input element. More to the point, if the normal use case forflatMapis producing a homogeneous array, then surely that applies tomap(and I guess by extension, non-tuple arrays in general) too? Is that discrepancy just due to being a technical simplicity/elegance thing?