Make Primitive Types Incompatible using Typescript Structural Typing

Tomasz Ducin
InstructorTomasz Ducin
Share this video with your friends

Social Share Links

Send Tweet
Published 6 months ago
Updated 2 months ago

Structural typing is when TypeScript looks at the shape of a type when comparing two different types. We can take advantage of this pattern by extending our type alias with a structural type that makes a primitive number type incompatible with our alias.

We'll explore how to work with structural types in this lesson!

[00:00] We can slightly modify the way TypeScript evaluates type compatibility across various types using TypeScript structural typing characteristics. That means if we want to change the type compatibility, we need to change the shape of a certain type by, for instance, extending the primitive with [00:19] properties. At first, this could sound slightly strange, but we have already mentioned that a primitive is going to be boxed automatically wrapped into an object if only JavaScript requires that. The whole point of what we're doing is that this information, this additional property is going to be [00:39] compile time only. This is not going to exist during JavaScript's run time. So just to illustrate how it works, let's declare 2 variables. 1 is going to be of type money, m for money. And let's also declare the n, which stands for number. What we want to [00:59] see is whether we can assign one to the other and whether it works or not. So we can see that money can be assigned into number, but not the other way around. Why is that? Each expression of type money satisfies 2 conditions. 1st, it's number primitive. 2nd, it [01:18] includes a property type of some unknown value, whatever. But on the other hand, number satisfies only one condition, which is basically being a number. So when we have a variable of type, an expression of type, money, then the requirement of being simply a number is [01:38] satisfied. Well, it additionally has some additional properties, but it's not relevant. The only thing that is required, and it's being a primitive, is satisfied so that it works. On the other hand, when we want to assign anything into the money type, then 2 conditions need to be satisfied. Both being the primitive and having the [01:58] additional property. And our number primitive satisfies the first condition, but it doesn't satisfy the other condition. In other words, we can think of types as being contracts, including 1 or more different conditions to be met. And this is how TypeScript evaluates type compatibility. [02:18] Now, it would be possible theoretically to add this type property with the unknown value into a certain object. So just to make it slightly more bulletproof, let's just provide it with a unique symbol so that it would be impossible from the outside to provide exactly [02:38] this symbol, this is because of how symbols work, and TypeScript requires us to annotate the type property with a read only modifier, so So just for the sake of correctness, let's annotate the type with a read only. But what is important is that we have made our type alias to [02:58] be compatible only one way. So that each money is a number, but not each number is a valid money value. So what can we do with such money type? Well, we can do the arithmetic operations such as add equals, and here we can [03:18] add m plus m. We can clearly see this is just a number since m itself is a number. We can also subtract and multiply, etcetera, etcetera. So we can see that due to the fact that money is a number, all of these work. However, if we want to create a money [03:38] expression, we cannot just assign anything because we're not sure whether this number value relates to money. We need to check it somehow. Generally, there are 2 directions in which we can deal with that. One would be to simply brute force the compiler by saying, hey, compiler, Don't treat [03:58] this expression as you would normally infer it to be the primitive number. Please subtype it so that it's not only a number, but it also includes one additional property. So this as money could also be wrapped into a function that accepts an argument, which is a [04:17] number, and essentially, it's going to do the very same thing, just we wrap it with a function so that whenever we want to modify the way that we verify whether something is money or not, this is essentially being wrapped in a function that we're going to reuse, but still there is no [04:37] check. However, there is a better way to deal with this.