Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Tom Chiverton
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Tom Chiverton@thefalken )

Inverse Type Guards Work In TypeScript

By Ben Nadel on

One of the things that I love about TypeScript is that it forces you to think harder about what kind of values you have at your disposal. One of the ways in which TypeScript does this is by forcing you to check the Type of value that you have before your code implicitly downcasts it to a Sub-class. This Type check is known as Type Guard. And, in all the Type Guard examples that I've seen, the condition is a positive assertion. But, the TypeScript compiler is smart enough to understand a negative assertion, and how it affects the flow of control after the guard block.

To be clear, when I say that most Type Guard examples use a positive assertion, I mean that they look something like this:

  • if ( value instanceof SomeClass ) {
  •  
  • // Inside this block, the TypeScript compiler will treat "value" as an instance
  • // of the "SomeClass" class, which means it's safe to access "SomeClass" properties.
  • console.log( value.someClassProp );
  •  
  • }

Here, I'm asserting that the given value is of a given Sub-class type. Which, in turn, allows me to access Sub-class properties safely within the bounds of the given block.

With a negative Type Guard assertion, I can break out of a method if the given value is not of a given type. Then, all of the code after the Type Guard will safely be able to access properties on the given type.

To see what I mean, take a look at this example:

  • class A {
  • public aProp: string;
  • }
  •  
  • class B extends A {
  • public bProp: string;
  • }
  •  
  • var a = new A();
  • a.aProp = "a1";
  •  
  • var b = new B();
  • b.aProp = "a2";
  • b.bProp = "b2";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • inspectValue( a );
  • inspectValue( b );
  •  
  • function inspectValue( value: A ) : void {
  •  
  • console.log( "A:", value.aProp );
  •  
  • // In this NEGATIVE ASSERTION Type Guard, we're asserting that the value is NOT
  • // of a given type; and, if so, we're breaking out of the method.
  • if ( ! ( value instanceof B ) ) {
  •  
  • return;
  •  
  • }
  •  
  • // The negative assertion Type Guard above becomes an implied POSITIVE ASSERTION
  • // for all code that followed the Type Guard. And, TypeScript is smart enough to
  • // understand this relationship.
  • console.log( "B:", value.bProp );
  •  
  • }

In this demo, the first half of the inspectValue() method knows that the given value is of type "A" because that is the type annotation on the parameter. We then have the negative assertion Type Guard which breaks out of the method if the given value is not of type "B". This negative assertion guard condition implies that the latter half of the inspectValue() method can assume that the given value is of type "B" (otherwise, the control flow would have executed the "return" statement). And, the TypeScript compiler is smart enough to understand this.

So, when we run the above code through the ts-node executable, we get the following output:


 
 
 

 
 Negative assertion type guards / inverse type guards work in TypeScript. 
 
 
 

As you can see, the TypeScript compiler did not complain that we accessed "value.bProp" on a parameter with an "A" type annotation.

TypeScript is awesome. It provides the code with a degree of self-documentation and forces you to think about the relationship of your objects to the consuming context. I'm just surprised the TypeScript compiler is smart enough to understand how type assertions - both positive and negative - impact the assumptions that can be made.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Good post. I use this sort of thing with union types quite frequently.
A contrived example

const myFunc = (p1: number | number[]) => {

if (!_.isArray(p1)) {
//here p1 must be number because of type definition for _ being similar to
// function isArray(x: T | T[]) : x is Array<T>
}

}

Reply to this Comment

@Ian,

That's pretty cool that TypeScript understands how to translate lodash's "is" functions into Type Guards. I'll have to look at the Definitely Typed library to see what the signature is. I'm just so impressed with what TS can do. Such a pleasure.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.