Nominal Types vs Structural Types // Creating Opaque Types

Pro Members Only

This lesson is available if you have an active subscription.

Alternatively, some member might be able to gift it to you.

If you already have a subscription, you can sign in.

Nominal Types vs Structural Types // Creating Opaque Types

Subscription Required

You must have an active subscription to access this content.
If you already have a subscription, you can sign in.

Simple System

This code has a simple bug not caught by TypeScript, can you see it?

export type AccountNumber = number;
export type AccountBalance = number;

function setupAccount(accountNumber: AccountNumber, accountBalance: AccountBalance) {
// ... setup the account
}

let accountNumber: AccountNumber = 1337;
let accountBalance: AccountBalance = 100;

setupAccount(accountBalance, accountNumber);

Nominal Types

Same example with Nominal types which can catch such issues at compile time:

export type AccountNumber = number & { _: 'AccountNumber' };
export type AccountBalance = number & { _: 'AccountBalance' };

const makeAccountNumber = (accountNumber: number): AccountNumber => accountNumber as AccountNumber;
const makeAccountBalance = (accountBalance: number): AccountBalance => accountBalance as AccountBalance;

function setupAccount(accountNumber: AccountNumber, accountBalance: AccountBalance) {
// ... setup the account
}

let accountNumber: AccountNumber = makeAccountNumber(1337);
let accountBalance: AccountBalance = makeAccountBalance(100);

setupAccount(accountNumber, accountBalance);

// accountNumber = accountBalance;
// accountBalance = accountNumber;

Nominal Types are also often called "Branded Types" because they have a brand attached (e.g. AccountNumber) and sometimes "Opaque Types" because they are not transparent like the underlying primitive (e.g. number)

javascript
typescript
react
playwright

Enjoy free content straight from your inbox 💌

No spam, unsubscribe at any time.

Transcript

00:00

Because TypeScript favors developer convenience over strict type safety. It misses out on a feature known as opaque types, which is something that is offered by a nominal type system. So in this lesson, we will look at what that means and how you can simulate opaque types in TypeScript. So let's go. It is not uncommon to have multiple types within your application or your database, which are backed by the same primitive structure as a use case. In this particular example, the account number and the account balance are both backed by the primitive number type. Here we have a function which accepts an account number

00:32

and an account balance, and is designed to set up an account for a particular user. This function is designed to highlight a very common mistake that is found in various programs. We get an account number and an account balance from somewhere, and then we use these values to set up a new account. I'll give you a few seconds to see if you notice any bugs. The issue here is that we've invoked set up account with the account balance being passed to account number and account number being passed to account balance. Now this is just one example of an issue like this.

01:05

Let's look at a key feature of TypeScript that makes mistakes like this possible. As far as TypeScript is concerned, the account number and the account balance are exactly the same type. That is the primitive type number, which means that you can assign one to the other without any errors from TypeScript. In a nominal type system, names matter. However, TypeScript is mostly a structural type system, so the names don't matter as long as the structure is the same. As far as TypeScript is concerned. The type is the same in order to make the account number and account balance no longer assignable to one another. A simple fix that we can do is we can add stuff

01:38

to their structure that makes them distinct from one another. So account number will have an underscore member that contains the literal string account number and account balance will have an underscore member containing the literal account balance. Now with these two changes in place, we can no longer assign one to the other. So we get an error when we set up the account with the wrong values as well as anywhere else where we might be assigning the value to the wrong type. However, this has resulted in one more issue where our types are no longer compatible with just numbers. So we cannot initialize these variables with numerical rules.

02:10

And if you think about it, this is actually a good thing. We don't want un typed numbers flying around in our code base. What we can do is that we can create utility functions that take a primitive number and then return the well-defined types. We don't have to actually add the underscore property to these numbers. We can simply tell TypeScript that treat this number as an account number or an account balance by using a clever type assertion. So whenever we want to create a number literal that we might get from a database or somewhere else, we can invoke these functions to convert these numbers to well-defined types. With these two changes in place, when we invoke the set

02:43

of function and provide the wrong values for the wrong parameters, we get a nice error from TypeScript. For example, here it's complaining that account balance is not assignable to the type account number, and we can catch and fix this issue at compile time instead of it blowing up in production. And of Course, any other invalid variable assignments will also be caught and we can remove this mistake. Now, these types are still compatible with the primitive types that they are derived from. For example, we can still do arithmetic on a balance value to get the double balance and tax triple understand that this new type will be of type number, and if you wanted to preserve the safety of

03:16

that opaque type, we can wrap this multiplication with a call to make account balance. Even though TypeScript doesn't have a dedicated syntax, phenomenal types, it's not that hard to simulate with a simple type intersection followed by a controlled type assertion.