Values, Types, and the counter-intuitive behavior of classes

TypeScript’s classes
TypeScript’s classes

If you wonder why this code compiles:

class User {  constructor(public name:string) {}}function isUser(user: User): boolean {  return user instanceof User; // should always return true?}// compiles even though we didn't do `new User`isUser({name: 'Georges'}); // return false

this article will explain it, and will provide some guidelines on how and when to use classes in TypeScript.

Values and Types

TypeScript handles values — things that will be present at runtime — and types — constraints on value that exist only at compile time. …

All examples were tested against TypeScript 2.8.3 with strictNullChecks and strictFunctionTypes turned on.

The original intent of this article was to explain how we can write a type Equ<A, B> that proves that A and B are equal. Spoiler alert: it's not really possible in a sound way in TypeScript. However, to fight publication bias I believe it's important to share what I tested and why it doesn't work.

The naive implementation

In other languages, Equ is often built with a type Equ<A, B> and one (and only one) constructor named Refl<A> which extends Equ<A, B>. In TypeScript, it could read as:

All the code in this article has been tested with typescript 2.7.2

Sometimes, the base types offered by TypeScript, like number or string, are not enough to express precisely what you want. For example, you may want to represent the age of a user with a type indicating that it must be a number greater than 0. Or maybe you want to represent an email, which is a string that should match a certain pattern. The answer comes under the form of refinement types, this article aims to show you how to create such types.

What are refinement types?

Refinement types are types defined…

All the code of this article has been tested with typescript 2.6.1

TypeScript compilation can sometimes yield unexpected results. Take the following code snippet, for example:

Why does userList compile and not user? It's not a bug: it has to do with structural typing, the paradigm TypeScript's type system relies on.

What is structural typing?

Most mainstream static typed languages (Java, C#, Scala, etc.) use a type system based on nominal typing — meaning that if two identical types are defined with different names, the type checker will return an error if you try to assign a variable of the first type to…

All the code here was tested with TypeScript 2.2.2

TypeScript offers native enumerations with an implementation that makes it an alias of number.

An example of such an enumeration could be:

There are some problems with the above code:

  • If we add an element in our enum (eg. BLUE), there will be no warning that we must also add a case in the display function;
  • We can call a function expecting a TrafficLightState with any number. For example display(5) will compile;
  • We cannot attach methods to the enumeration without doing some horrible monkey patching.

All these shortcomings lead to…

All the code present in this article was tested with TypeScript 2.2.1 and Lodash 4.6.1

The emergence of static type checking in JavaScript has put an end to the dynamic programming approach. If you had the following snippet in your JavaScript

for it to work in TypeScript, the only solution is to cast unknownObject to any? Or is it? Spoiler: No, and this article will show you how to keep this kind of dynamic code without losing any type checking goodness.

Literal Types

A value can be of multiple types. For example:

In the last line of this example, "foo"

Benoit Lemoine

I’m a full-stack developer, in love with functional programming and type systems. I’m working currently at Decathlon Canada, in Montreal QC.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store