Typescript: SUGGESTION: Set and Map '.has' method as type guard

Created on 27 Sep 2017  路  7Comments  路  Source: microsoft/TypeScript

This is a small suggestion, but I think it can be interesting to bring this type of characteristics to the spirit of the typescript language.

Code

function actual(value: string|number) { 
    let numbers = new Set<number>();

    if (numbers.has(value))  // Error: Type 'string' is not assignable to type 'number'.
        value  
         // we need to do
    if (numbers.has(value as number)) 
        value = value as number 
}

declare class MySet<T> {
    has(elem: any): elem is T;
}

function suggested(value: string|number) 
    let numbers = new MySet<number>();

    if (numbers.has(value)) // OK, we are doing a legal checking
        value // <= value is a 'number'
}

thank you for your attention.

Suggestion Too Complex

Most helpful comment

I don't think this is a great idea, for a few reasons. The most obvious one is this:

declare let numbers: Set<number>;
declare let value: string | number; 
if (numbers.has(value)) {
  value + 2 // narrowed to number, okay
} else {
  value.charAt(0); // narrowed to string?!
}

If a Set<T> doesn't contain a value, then the value isn't of type T?

Less obvious but possibly more important is that such a declaration subverts the point of type checking. Mistakes like this will no longer be caught:

let nums = [0, 1, 2]; // array of numbers
numbers.has(nums); // oops, no error!

So I wouldn't want to see this added to the standard libraries.


If these objections don't sway you and you still find this change useful for your own work, you could always modify or augment your local declaration of Set:

// put this in your own code to reap its benefits
interface Set<T> {
    has(elem: any): elem is T;
}

Cheers!

All 7 comments

I don't think this is a great idea, for a few reasons. The most obvious one is this:

declare let numbers: Set<number>;
declare let value: string | number; 
if (numbers.has(value)) {
  value + 2 // narrowed to number, okay
} else {
  value.charAt(0); // narrowed to string?!
}

If a Set<T> doesn't contain a value, then the value isn't of type T?

Less obvious but possibly more important is that such a declaration subverts the point of type checking. Mistakes like this will no longer be caught:

let nums = [0, 1, 2]; // array of numbers
numbers.has(nums); // oops, no error!

So I wouldn't want to see this added to the standard libraries.


If these objections don't sway you and you still find this change useful for your own work, you could always modify or augment your local declaration of Set:

// put this in your own code to reap its benefits
interface Set<T> {
    has(elem: any): elem is T;
}

Cheers!

It's not a duplicate; #13086 is about performing a get() after a has(). This is... something else.

Is there a very compelling scenario for this? I get the idea, but like @jcalz said, this means you could easily screw up and pass anything accidentally which lead to some frustrating behavior.

There's nothing super wrong with adding your own overload if you feel it'd help you out a lot.

Wouldn't the narrower use-case, where has is just used just as non-undefined typeguard, still be really useful? I often have situations along the lines of:

function foo(map: Map<String, String>, key: String): String {
  if (map.has(key)) {
    return map.get(key); // Type 'String | undefined' is not assignable to type 'String'.
  }
  return "";
}

It feels odd and incomplete that TypeScript doesn't know that map.get(key) in the if's clause must return not-undefined thanks to that has.

@fasiha you are looking for #13086

Agree with @jcalz this doesn't seem like a good solution (to say nothing of the problem)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

siddjain picture siddjain  路  3Comments

blendsdk picture blendsdk  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

jbondc picture jbondc  路  3Comments

remojansen picture remojansen  路  3Comments