question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

flatMap errors when callback returns certain union types

See original GitHub issue

TypeScript Version: 3.4.3

Search Terms: flat, flatmap

Code

// Map<string, number[]>
const myMap = new Map([
  ['foo', [1, 2, 3]],
  ['bar', [4, 5, 6]]
]);

// string[]
const myArray = ['foo', 'bar', 'baz'];

// (key: any) => number[] | undefined
const mapFn = key => myMap.get(key);

const flatMapped = myArray.flatMap(mapFn);

Actual behavior:

With strict null checks on:

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:

// (number[] | undefined)[]
const mapped = myArray.map(mapFn);

// any[] !!
const flattened = mapped.flat();

Expected behavior:

flatMapped/flattened result to be inferred as (number | undefined)[].

Playground Link:

Doesn’t seem to be able to load the appropriate library definition, even with /// <reference lib="es2019.array" />

Related Issues:

#29604 seems to be related to the .flat() part.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

5reactions
RyanCavanaughcommented, Apr 19, 2019

It models a certain statically-typed interpretation of the behaviour, but there’s nothing in the ES spec that prevents it.

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 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.

2reactions
noinklingcommented, Apr 20, 2019

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?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Typescript infers the wrong return of a method? - Stack Overflow
It is inferring x wrong saying it is of type number when it should know that data is of the type Loaded ....
Read more >
Configuring error types when using flatMap in Combine
Combine doesn't like it when your flatMap returns a publisher with an error type that does not align with the upstream publisher.
Read more >
Values and errors, part 1: 'Result' in Swift - Cocoa with Love
Both types can be processed via map and flatMap to handle the success case while short-circuiting the failure case. In many ways, a...
Read more >
Untitled
The union type informs TypeScript of all possible values for kind, ... flatMap (function callback (current_value, index, Array)) { // It returns Dec...
Read more >
A declarative approach to error handling in Typescript
Should it return any or a specific union type? All of those options will only complicate things for us, as later in the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found