Schrödinger's cat explains TypeScript<Generics>
Tags: typescript
Published: 2021-03-27 14:16:00

In 1935, Erwin complained to Albert about a problem in Quantum Mechanics. The bug report is now known as the [Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat) thought experiment and the fix, to *shut up and calculate,* is still deployed in production. Many of those calculations involved matrix multiplication of multidimensional vectors and that took a lot of space on blackboards. Thus, Paul established the [⟨bra|ket⟩](https://en.wikipedia.org/wiki/Bra%E2%80%93ket_notation) notation to help the cool kids make fewer mistakes.

In 2013, library authors complained to Jonathan about a problem in TypeScript. They wanted to make their utilities more reusable, without [*any*](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any) compromise. So starting with version [0.9](https://www.theregister.com/2013/06/18/typescript_update_0_9/), we can now *shut up and [\<Generics>](https://www.typescriptlang.org/docs/handbook/2/generics.html)*.


# Union cats

Let's imagine writing a library, "Quantum Cats," that checks whether a `cat` is dead or alive:

```ts
type Cat = {status: 'dead'|'alive'}

function checkCat(cat: Cat) {
  return cat.status
}

// inferred type: 'dead'|'alive'
const result = checkCat({status: 'alive'})
```
> [TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsKBeKBvAzsBBXdAuKAcgBMI5jCAfQuAGwEsA3CQgXwFgAoLgM2wDsAxsHoB7flEEALCIIDW8YAApBCAooCUaLlCgAnCMGx6Jq4ADpMOdFw7dOAegdR6-HhD0HiUUJAIkyCmo6JhYuQXFMfQh0bFpEFGlZBQQlDCwjfCIQ5jYNIA)

And for a while, [union types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) will be all that we need.


# Generic cats

However, our users expect no cats to be harmed in the process of checking! There is not just one type of cats that are all "dead or alive," we need to distinguish different types of cats:

```ts
type Cat<Status> = {status: Status}

function checkCat<Status extends 'dead'|'alive'>(
  cat: Cat<Status>
) {
  return cat.status
}

// inferred type: 'alive'
const result = checkCat({status: 'alive'})
```
> [TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsAPAZWAgrgZwHxQLxQDemawWAXFKhpgL4CwAUEwGboB2AxsAJYD27KJwAWETgGt4SamUxQIAD2AR2AEzkByVRDiqNAHw1wANjwBuEDdgAUTKEISUpKUlmxMAlETtQAThDJfQU4EADoSGiYGZkYAelioHnYWCF9-VShQSEojUwsNJk4BEj8ITHRjYHwhUQkpa2JXTByTc0taDyA)


## Reading the code

Our users are now happy, but we have to explain what we did to our future selves after lunch. As a first step, let's try to read the code above. Everyone might read it differently, and that's fine, here is just an example:

* we have a type `Cat`, that is a generic
  * it takes a parameter, type `Status`, which is implicitly `unknown`
  * value of type `Cat` is an object with one property, `status`
  * type of that property is `Status`
* then, we declare a function `checkCat`, that has a generic type
  * the function itself takes one parameter, `cat`
  * the function type takes one generic parameter, type `Status`
  * `Status` extends a union of 2 types, both are string literals, namely `dead` *or* `alive`
  * the function parameter `cat` has a type, which is an instance of a generic that we get by invoking `Cat` with parameter `Status` inside the angle brackets
      * note: just like in HTML, we write the angle brackets using less-than and greater-than signs `<>`, not the Unicode characters `⟨⟩`
  * the function contains 1 return statement
  * the return expression is `cat` dot `status`
  * the return type is inferred
* then, we make an assignment to a constant variable `result`
  * the right-hand side consists of a function call
  * `checkCat` is called with one argument, an object literal that has one property, `status`, whose value is a string, `alive`
  * type of the function is inferred from the argument
  * type of the new variable is also inferred


## Understanding generics

Now that we can read the code, what to make of it? Let's think of generics as higher order types, a concept similar to [higher order functions](https://redd.one/blog/thinking-in-functions-higher-order-functions) (when a function returns a function, or if it takes another function as a parameter to call back):

> #### A generic is a type that accepts one or more types as parameters and it returns a different type based on those.

```ts
type Cat<Status> = {status: Status}

type CatAlive = Cat<'alive'> // same as {status: 'alive'}
type CatDead  = Cat<'dead'> // same as {status: 'dead'}
type CatUndefined = Cat<undefined> // {status: undefined}
```
> [TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsAPAZWAgrgZwHxQLxQDemawWAXFKhpgL4CwAUE6JLAgIIA2AlgG7QC8JAHI4vASNwB6aVExwAttDiYiJGpTESIIho1bRhAEQhwAJlHztR5s+alRZ8pSrXFSFKCLsW9LcCMEAFUAOzsAMx5QiEshBER0cIgomPMZOQ9NKCTI6NjaIA)

We can restrict what types to allow in the parameters (`extends`), and we can provide a default type (`<... = default>`):

```ts
type Cat<Status extends 'dead'|'alive' = 'alive'> = {
  status: Status
}

type CatAlive = Cat // {status: 'alive'}
type CatDead = Cat<'dead'> // {status: 'dead'}
type CatDeadOrAlive = Cat<'dead'|'alive'> // CatDead | CatAlive

// not possible because of type error:
type CatUndefined = Cat<undefined>
```
> [TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsAPAZWAgrgZyhAHsCAOwBNsByYiOYsgHzLgBsBLANwjKgF4oGX2yAPm5QA3gFgAUFCiY0wLAC4oqDJikBfKVNCRYCAIL9oPeMCgB6C2LlrlfNhy2Td0MwBEqxEWcQUvQpbWorYKmPaU1GTOrvrAntQA8gBORo4+CH6RNPRMjoFWcQnetHFp7NqShYQA9uZgNZiYzABGjNAtEADGcFjQNQBmULEQyck1yYo64G4IAKokEAPMhBDeppnoi8urxIJAA)

We can use generics to define function types:

```ts
function checkCat<Status extends 'dead'|'alive'>(
  cat: Cat<Status>
): Status {
  return cat.status
}

const checkCat = <Status extends 'dead'|'alive'>(
  cat: Cat<Status>
) => cat.status
```
> [TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsAPAZWAgrgZyhAHsCAOwBNsByYiOYsgHzLgBsBLANwjKgF4oGX2yAPm5QA3gFgAUFCiY0wLAC4oqDJikBfKVIBm6QgGNgzAPaEoBgBYQDAa3jAAjCnlYc+IqV6VqdPmw5BAAopGQMEZQcXNUEpAEplVQVsCWkoACcIBXTzcOAAOjk1TW1JAzM5C2s7BwAmEWjk9wISch8aeiYAoRC0vMiERqxYyTjuYTzC10wgA)

<br>
> *Note:* `<Status>() => {}` will not work in `*.tsx` files, that is ambiguous with a JSX opening tag, but we can use `<Status extends unknown>`.

We can use generic instances inside other generics:

```ts
type Cat<Status> = {status: Status}

function isCatAlive<C extends Cat<string|undefined>>(
  cat: C
): cat is C & Cat<'alive'> {
  return cat.status === 'alive'
}
function feedCat(cat: Cat<'alive'>) {
  return '😻'
}

// inferred type: {status: string}
const cat = {status: 'alive'}

// @ts-expect-error
// Type 'string' is not assignable to type '"alive"'
feedCat(cat)

if (isCatAlive(cat)) {
  feedCat(cat) // ✔
}
```
> [TS Playground](https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=21&pc=2#code/C4TwDgpgBAwghsAPAZWAgrgZwHxQLxQDemawWAXFKhpgL4CwAUEwGboB2AxsAJYD27KD0zxgAQQA2PAG4REMKBAAewCOwAmmWAkQkATj3YBzAD4d1EFoYjrs2ABRMoUTgkowmASkqvgQrQoAZNpIAORwUrKhuIROUHoQZHqCvgB0JDT4eAThkRChTAyMbFy8AlAsEDai9r7uOrky+dieRHEJSYKhgLwbgNx7BYxFTAD0w0LslXoJ6lCgkJTEpBRQ+oZGRZwCJC4I+EQZZJiUjVFDjKNQAALAmAC0ypDc91N8eiNjACrg0KGrxqH+KDsPh+OCYTA8IzsOAAIwk0GAfFm3ygoQARBEmmiBpVqghaghPEwmDwWFB7MJRJImgTgJ5WrFGM5ceoar5WhdACjkhSAA)

And we can mix and match generics with [function overloads](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads) for our wildly reusable JavaScript utilities, even if we only know how to type some of our use cases, we don't have to be purrfect:

```ts
type Cat<Status = string> = {s: Status}

function checkMany<S1>(cats: [Cat<S1>]): [S1]
function checkMany<S1, S2>(cats: [Cat<S1>, Cat<S2>]): [S1, S2]
function checkMany(cats: Cat[]): string[]
function checkMany(cats: Cat[]) {
  return cats.map(({s}) => s)
}

const result1 = checkMany([{s: 'a'}]) // inferred type: [string]
const result2 = checkMany([ // inferred type: ['d', 'a']
  {s: 'd' as const},
  {s: 'a' as const}
])
const result3 = checkMany([ // inferred type: string[] 🤷‍♂️
  {s: 'a' as const},
  {s: 'a' as const},
  {s: 'a' as const}
])
```
> *[TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsAPAZWAgrgZygXip4AJwEsA7AcwD5coBvTALilQ0wF8BYAKG4DN1SAY2DEA9qSiCAFhEEBrALJxSIFAEZKACkEJGUANrwkyDQF0AlE30nTfAcLETpsxctUmANMwBMWncD1DBHVKLyMUXwsrTx9bLn4hEXFJGXklFW1dJiN9KPwiMnJcu0THFJd0kEyA7IRc8zpuKChCCGB0QiddADoAWzgwTU16Ngacakxzbk4eLkFxAhaITHQAG2A1Gmc0t019eiYAcjhDtgsoAHoLqDJeCEJWgBMoUEgrAhIKOPnSRdaV9beLapVwZfSXa63e5PF7gCBWQ6PQ5eY6HOLNA5QRGHKBwbA-AhsDxNOh6VG4-ELYAzCzcAnAJYA4AAZmBFV24KuN1IdweEGer3h+U+RVMUEAfBuAdl3ALAEgCEyQDwfyTMeS8ZIqUSlWSThS1b9qcSuBitTjVfSaeYgA)*

# Quantum cats

So a few feature requests later, out code might look like this:

```ts
type Cat<Status = 'dead'|'alive'|undefined> =
  Status extends 'dead' ? {status: Status, bury: () => string}
  : Status extends 'alive' ? {status: Status, feed: () => string}
  : {status: Status}

function checkCat<S extends 'dead'|'alive'>(cat: Cat<S>):
  Cat<S>
function checkCat(cat: Cat<undefined>, probability?: number):
  Cat<'dead'|'alive'>
function checkCat(cat: Cat, probability = 0.5) {
  if (cat.status) {
    return cat
  }
  if (Math.random() < probability) {
    return {status: 'dead', bury: () => '😿'}
  }
  return {status: 'alive', feed: () => '😻'}
}

const quantumCat = {status: undefined}

function simulator() {
  const cat = checkCat(quantumCat)
  return cat.status === 'alive' ? cat.feed() : cat.bury()
}
const multipleWorlds = Array.from(Array(100_000)).map(simulator)
const statistics = multipleWorlds.reduce((acc, result) => {
  acc[result] = acc[result] ? acc[result] + 1 : 1
  return acc
}, {} as Record<string, number>)

console.log(statistics)
```
> *[TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwghsAPAZWAgrgZygXigcgBMI5D8AffOAGwEsA3CC9AO2IDNaWJCA+XAFBQoqDNggAPYBDbYiJMlAD8UAN6Y0wLAC4RmrABooAI3QAnELoAUASlz8NZrgHMAvkKi7RW8VJmE5GgYmZTUNMS99TCN2CB5rOxwHYCcWNw9ddSjIsXcBdlYAY2BaAHsWKEKACwhCgGt4JGQoSWlZAmJSCio6RnxeK0KEXUaUXhttD1HkXnyikvLKmvrGweHYBERWDi4eXiMwM1LjOGNaOlAlXRZ0AFtjCDMJqc35LsogvtmClmKyiuqtQaCDWwBGCAORxOZwuIFwUAADAA6ACsdlUHlo7CgoKR4R86I8wjMEC0ZgBCA87mEWJxAFkEFUkWY4GxSrdbFBEFBDsdTudaKBCcJiaTzBUshEOgp8EZTBYEvYCIBeDcA-Hv4alQTUkskS-E6AifJgxOKERVJFWAbj2NQI8oVyhooABHdCsrS3RrwyU+XTbCCcbiEPI-P6LTC0W7oagIUpmTkY4T2liOobAeGAlYgl1uu6NGweHXiyoIPFRXA4PA9YL4UKppGxHic3R1+UgWy2gRJx2R6glMDUCAAdVj1AC8IAgmYWSB60cOZPp1YAIwIhEAfVXCJsNiRtzgYCs4Z7MaenYdafxtA0tEK2DwPb7A+HZlHmGZPHQhQgViscEKhSMElMCjYBEn4BMoD-QoAG0gJAgBdeEoNgiBgN7RCVGQuD0KgABqKAl08AiCzFclIP-W0jFUVxIOwAAlWpY0IRBHBcIwbnuR5xgEM9k1KAckWoUpnEPTQrxKW98yAA)*

We introduced some [conditional types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) and [type narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) to be able to call `cat.feed()` without TypeScript errors about the foolishness of feeding potentially dead cats. Hopefully, we can still read the code.

---

👋 *I am Peter, a.k.a. [Aprillion](https://github.com/Aprillion/). I considered transcribing the last code example into English sentences, but that would be worth a separate post (or even a youtube video) if enough people are interested. Please let me know in the comments here or on [Twitter](https://twitter.com/aprillion0042).*