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<T>.filter should be able to refine T|undefined to T #20707

Closed
Firehed opened this issue Dec 14, 2017 · 5 comments
Closed

Array<T>.filter should be able to refine T|undefined to T #20707

Firehed opened this issue Dec 14, 2017 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@Firehed
Copy link

Firehed commented Dec 14, 2017

TypeScript Version: 2.6.1

Code

interface EntityType {
  id: number,
  name: string,
}
const ids = [1, 2, 3]
const entities = new Map<number, EntityType>()
let relevantEntities: EntityType[] = []
if (ids) {
  relevantEntities = ids.map(id => entities.get(id)).filter(Boolean)
}

Note that I have tried many variants of the argument to filter (x => !!x, x => x !== undefined, etc). I'm only using Boolean here since it works in Flow

Further note that making the following change allows the error checker to proceed, but only through basically lying about the data:

if (ids) {
  // get(id) becomes get(id)!
  relevantEntities = ids.map(id => entities.get(id)!).filter(Boolean)
}

Expected behavior:
Assignment proceeds without error, since any undefined values produced in the map operation are filtered out.

Actual behavior:

error TS2322: Type '(EntityType | undefined)[]' is not assignable to type 'EntityType[]'.
  Type 'EntityType | undefined' is not assignable to type 'EntityType'.
    Type 'undefined' is not assignable to type 'EntityType'.
@RyanCavanaugh
Copy link
Member

This is already possible, you just need to define an explicit user-defined type guard:

function notUndefined<T>(x: T | undefined): x is T {
    return x !== undefined;
}
if (ids) {
    relevantEntities = ids.map(id => entities.get(id)).filter(notUndefined);
}

@Astrophizz
Copy link

Astrophizz commented Dec 15, 2017

That only works if you pass the type guard as a function reference and don't, for example, call it with an arrow function. This does not work:

function notUndefined<T>(x: T | undefined): x is T {
    return x !== undefined;
}
if (ids) {
    relevantEntities = ids.map(id => entities.get(id)).filter(entity => notUndefined(entity));
}

As far as I know, if you need or want to use an arrow function you need to tell the compiler that you know better than it with an assertion:

function notUndefined<T>(x: T | undefined): x is T {
    return x !== undefined;
}
if (ids) {
    relevantEntities = ids.map(id => entities.get(id)).filter(entity => notUndefined(entity)) as EntityType[];
}

@Firehed
Copy link
Author

Firehed commented Dec 15, 2017

Thanks @RyanCavanaugh - that solution does appear to work as-is.

It is, however, fairly unintuitive (hence my being here in the first place!) and I think it would be immensely helpful if this "just worked" out of the box. I think it's a fairly common pattern, particularly when working with a normalized store of entities (e.g. Redux).

At the very least, having this documented on the Typescript website (rather than digging around in github issues and stackoverflow) would go a long way for the language's usability, even if it's not (currently?) practical to add first-class support into the language.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 10, 2018

Duplicate of #20812

@mhegazy mhegazy marked this as a duplicate of #20812 Jan 10, 2018
@mhegazy mhegazy added the Duplicate An existing issue was already created label Jan 10, 2018
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants