Structural type system and polymorphism in TypeScript. Type guards with predicates

TypeScript

TypeScript is a superset of JavaScript. Any JavaScript code is a valid TypeScript code, as long we set the compiler not to be strict. Therefore, TypeScript aims to be as flexible as possible so that it can apply to various situations. In this article, we look into type compatibility in TypeScript and explain what a structural type system is.

Type Compatibility in TypeScript

There are a lot of languages with a nominal type system. The above means that the two variables are compatible if they are of the same type. Let’s examine this C# code:

The above C# code causes an error. Due to a nominal type system,   and   are not compatible. Similarly, such situations occur in languages like Java and C++.

The above behavior might help us to prevent mismatching types, but it is not very flexible. To give us more freedom, TypeScript implements a structural type system.

Structural type system

In a language with a structural type system, two types are compatible judging by their structure, instead of the name. The above allows TypeScript to adjust to the way that we often write the JavaScript code.

Above, we simplify the assignment of properties in the constructor of the   class with the use of the   keyword

In TypeScript, the above code is perfectly valid. Going even further, we can use types that are not identical when it comes to its structure.

Structural subtyping

For one type to be compatible with the other, it needs to have at least the same properties.

Above, we have the   function. Since the   has all the properties of a  .

The fact that the   is compatible with the  does not mean that it works the other way around.

Argument of type ‘Person’ is not assignable to parameter of type ‘Employee’.
Property ‘workplaceType’ is missing in type ‘Person’ but required in type ‘Employee’.

The above happens because the  does not have all the properties of the  .

Similar subtyping happens when the    extends the  .

In languages with nominal subtyping, it would be the only way to achieve compatible subtypes. Thanks to TypeScript being a structurally typed language, instead, “accidental” subtypes work issue-free.

Polymorphism

Calling the   function using the   is an example of polymorphism. Since we know that the employee has all the properties of a person, we can treat its instance as such.

The most straightforward way to visualize it is with the use of an example with shapes and calculating their areas.

Although neither  nor  extends   explicitly, all of them have the   function. If that’s all we need, we can treat  and  as a .

Differentiating types with Type Guards

We don’t always have such straightforward types. Sometimes, we need to deal with unions and the  . Thankfully, TypeScript has mechanisms to help us with that. Such a situation might occur when we fetch the data from various APIs. Let’s say we want to fetch a user and print his workplace type if he is an employee.

Unfortunately, the above does not work. We experience an error:

Argument of type ‘unknown’ is not assignable to parameter of type ‘Employee’.
Type ‘{}’ is missing the following properties from type ‘Employee’: name, workplaceType

This is because we are not sure if what we fetch is a proper employee. At first glance, we might want to check the existence of the   property.

The above does not work either, because  . Even if we could check this property, the compiler wouldn’t treat it as a proper .

We also can’t use the   operator, because the  is just an interface. A solution to this issue are Type Guards.

Defining Type Guards

A type guard is a function that guarantees a type during a runtime check.

The   is a type predicate. Type predicates are a special return type that signals the type of a particular value.

Now, we can easily implement it in our logic to make sure that what we fetch is a proper .

In the above code, we check the type in the runtime so that we can safely call the   function.

We can make our code a bit cleaner using the   operator.

The in operator returns true if the specified property is in the specified object or its prototype chain.

Summary

In this article, we’ve reviewed two types of systems: structural and nominal. We’ve also looked through the consequences and reasons of TypeScript having the structural type system. We explained what polymorphism is and how we can apply it to TypeScript without extending interfaces explicitly. To help us in some situations, we’ve used type guards with type predicates and the  operator.

avatar
  Subscribe  
Notify of