Do you really know TypeScript? (1): Thinking in sets
Tags: typescript, programming, productivity, webdev
Published: 2021-12-25 17:14:29

I know many people, including myself, whose first experience with TypeScript was to write annotations in some variables and to add `as any` until the transpiler stopped complaining.

But at some point you realize that you **really** should dig a little deeper into TypeScript and finally understand those errors.

It's true that you can technically build the same websites/systems with and without TypeScript, but its benefits are huge:
- Catch errors at compile time instead of at runtime
- Autocomplete ("it was `user.phone` or `user.phoneNumber`?" 🤔)
- Language services (enabling some IDE's refactors for example)
- Better domain modelling

Using TypeScript is not only investing in the code's maintainability, it is also investing in the developer's productivity.

Okay, you are (or were already) convinced that TypeScript is worth learning, what is this series for?

In this series I will go through many aspects of the language that were not obvious to me even after having already done some projects with it.

**You will stop wrestling with TypeScript 🤼‍♀️**

## Seeing types as sets of values

```typescript
// what can we assign to it?

const foo: Bar = ???
```

We can assign any value that belongs to a subtype of `Bar`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vi9ps43y8t4p48cl1m2.png)

One cool thing of this system is that you can use set operators (among others) to create new types.

For example the union operator: `number | string` is the set of all numbers and strings. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnw4kqmr4qub7mfgxxo6.png)
 
## Structural typing

To know if a value belongs or not to a type, TypeScript only focuses on the shape that it has. 

```typescript
class Person {
    name: string
}

// We didn't use the "new" keyword
const person: Person = {
   name: 'Jame'
}
```

That wouldn't be possible in nominal type systems.

Let's see a slightly harder example:

```typescript
type File = {
    name: string
    extension: string
}

type Folder = {
    name: string
    color: string
}

type DesktopItem = File | Folder
```

`DesktopItem` contains all the objects that either have the properties (name and type) of `File` or `Folder`.


```typescript
const item: DesktopItem = couldBeFileOrFolder

// should work, right?
givenItem.extension
```

It doesn't work because only `File` has that property and the specific type of `item` could be `Folder`.

When we declare the union of two types the result is a type that has the intersection of the properties. In this case objects with a property `name` of type `string`, because it is the only property that they have in common.

It's also true the other way around: The intersection of two types results in a type that has the union of the properties.

```typescript
keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)
```

## The empty set and the universal set

If we can think of types as sets of values, what types are the empty set and the universal set?

`never` is the empty set and `unknown` is the universal set.

These are both very special and we'll talk about them soon.


## Things to remember

- Types are sets of values
- One value of type `Y` is assignable to a type `X` if `Y` is a subtype of `X`.
- Type compatibility in TypeScript is determined by structural typing.

## Resources to go deeper
- [Effective TypeScript](https://www.goodreads.com/en/book/show/48570456-effective-typescript)
- [TypeScript docs](https://www.typescriptlang.org/docs/)