Type Enthusiast's Notes about TypeScript. Part 1. Typing in Anger

Posted on December 12, 2021 by Robert Peszek
Revision History:

Please Leave Feedback in: git discussions

Disclaimers: (imagine this is a very small font, read it very fast in a half-whisper)
I assume strict compiler flags are on, something you get by default with scaffolding, e.g. using create-react-app my-project --template typescript is close enough.
The code examples have been tested with TypeScript v4.4.4, v4.5.2, and v4.6.3.
office.js examples are based on https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js and @types/office-js@1.0.221 (these match the current scaffold for office.js/React).
This post is a pandoc conversion of markdown document and code examples are not interactive.
Most of the code examples are published in ts-notes folder in this github repo: ts-experiments.

Introduction to the series

“TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the programmers at Microsoft could bring traditional object-oriented programs to the web. As it has developed, TypeScript’s type system has evolved to model code written by native JavaScripters. The resulting system is powerful, interesting and messy.

From typescriptlang TypeScript for Functional Programmers

I wanted to write a short post about my experience with TS types, I ended up with a draft the size of a short book. I decided to split it into digestible installments and publish it as a series of shorter posts. The series will be about the powerful, interesting and messy types in TS. This post is the first in that series.

Here is my plan:

  • Part 1 (this post). Is a warm-up. Part 1 has been motivated by a project at my work that uses TS. I will show code examples that are hard to compile. I will discuss strategies and methods for resolving compilation issues. I will present code examples that compile but really, really should not, and code examples that should compile but surprisingly don’t. I will also summarize my overall experience of working with TS.
    This series needed a JS library with TS bindings to draw examples from, I decided to use office.js and Part 1 introduces it.
  • Part 2. Will be about keeping types honest. Are runtime values consistent with the types? We hope they always are but, especially in a gradually typed language like TS, types will sometimes lie. We will see concrete examples of type dishonesty from office.js. Part 2 will cover the notorious any and its safer cousin unknown, the type coercion (casting), and TS’s type guards. I will also discuss (or rather rant about) coding conventions for transparent, self documenting types.
  • Part 3. Will cover some of the TS type safety features that I absolutely love. Throughout the series, we will encounter several examples where TS compiler does not work as expected. This part will discuss questionable (and arguably incorrect) semantics of subtyping variance and of narrowing. It will argue that what TS is and does it quite complex. Complexity is the likely cause of errors in TS programs and in the language itself.
  • Part 4, Part 5. Will be more theoretical. Notes in Parts 4-5 will discuss topics such as TS’s structural, recursive types, subtyping, phantom types, type variable scoping, higher-rank polymorphism (TS supports a version of it!), and type level programming. I will show a trick to increase type safety that prevents widening to unknown or other supertypes.
  • Part 6. Will be a wrap-up with some final thoughts.

Why am I writing these notes?
To be honest, it is because I am really impressed and excited about some of the type safety features in TS.

Despite being a superset of JavaScript, TS stands out among mainstream languages as one that supports some interesting types.
There exist a tiny but important feedback loop: the more developers play with types the more they will end up being used.
So, to be perfectly honest, the goal of these notes is to simply play with some interesting types and see how the compiler reacts.

IMO, to master something is to understand its limitations.
So, to be brutally honest, the goal of these notes is to explore the TS compiler limitations.

Target audience and prerequisites. I assume that the reader is interested in types and either uses or considers using TypeScript.
Types tend to be related to FP. There will not be much FP in these notes. However, I will use some basic functional programming concepts, like currying, without explaining them.
TypeScript is a superset of JavaScript with type syntax very similar to any other C-like language. These notes will probably be hard to read without some experience with JavaScript or ability to read C-like types.

About the author. I am spearheading a rewrite of a legacy frontend component at work, the goal is to rewrite it using the new React.js and TypeScript. In recent years I have been spending all of my time in the backend designing, writing, and maintaining Haskell programs. Haskell code has a lot of types. Thus, I use types a lot. Types allow me to code faster, safer, and with much more confidence.
I wear a hat with types on it when writing TS.
I love Programming Language Theory and have some experience and lots of interest in compiler and language design.
I wear a very thin headband embroidered with PLT symbols under my hat (should be mostly invisible in this series).
All of this gives me a different (compared to most typescripters) perspective and a reason to write these posts. For some readers, parts of these posts will feel strange. Established practices like overloading will be considered a bad thing, writing experimental code (that won’t even run) to answer type questions will be a good thing. Strange is a corollary of different.

What is TypeScript for? Is it just a JavaScript add-on used to prevent typos and trivial code errors?
Or, will TypeScript more fundamentally change the way the code is written?
Please have these questions in mind when reading these notes.

We will cover a lot of topics.

“And we never say anything unless it is worth taking a long time to say.”

J.R.R Tolkien and Treebeard about discussing types in TypeScript

TypeScript is great!

It literally took me less than one minute of playing with TS to get excited about it.
Just look at the union types (using a somewhat contrived example):

type Person = {firstNm: string, lastNm: string} 
type NullablePerson = Person | null

const getName = (p:NullablePerson): string => {
    //const tst1 = p.firstNm //does not compile
    if(p===null){
        //const tst2 = p.firstNm //does not compile
        return "John Smith"
    } else {
        return p.firstNm + " " + p.lastNm //compiles
    }
}

How cool!

Talking about my “literal” excitement, my next play example implements Either (I am not trying to implement my own Either type, only to play with the language):

type Either<A,B> = 
| {type: "left", content: A}
| {type: "right", content: B}

const x1: Either<number, string> = {type: "left", content: 1}
const xone: Either<number, string> = {type: "right", content: "one"}
const wrong: Either<number, string> = {type: "left", content: "one"} // does not compile

it almost looks like dependent types! TS calls these literal types. (In this example, "left" is a type with a single value "left": "left".)
TypeScript calls this programming pattern Discriminated Unions.

And, TS is serious about string property names too:

const y: Either<number, string> = {"type": "left", "content": 1}
const wrong: Either<number, string> = {"type": "left", "content": "one"} // does not compile

TypeScript ts-pattern library uses discriminated unions to implement pattern matching. Exhaustive check is part of it.
Again, really cool. All of these are really exciting developments to me.

Continuing with play examples, here is the full JSON grammar defined in TS.

type JsonVal = 
| {type: "object", val: Map<string, JsonVal>}
| {type: "array", val: JsonVal[]}
| {type: "string", val: string}
| {type: "number", val: number}
| {type: "bool", val: boolean}
| {type: "null"}

const tstj: JsonVal = {type:"array", val:[{type: "null"}, {type: "number", val: 5}]} //compiles
const wrong: JsonVal = {type: "number", val: {type: "string", val: "5"}} //does not compile, number is not JSON object
const wrong2: {type: "object",  val:[{type: "null"}, {type: "number", val: 5}]} //does not compile, object is not an array

This could have been expressed with OO classes, but it would not be very easy, would it?
I wrote the JsonVal definition without thinking, I have committed Data.Aeson.Value (Haskell’s commonly used type for JSON values) definition to memory and I just mimicked it. Then I looked at it again … holly … TS supports complex recursive definitions! We will discuss recursive types later in this series.

TypeScript has an ability to do type level programming that goes beyond the demonstrated uses of literal types. All of this is oriented toward creating type safety over various kinds of idiomatic JS code and is limited in scope. It is nonetheless interesting. We will return to this topic in the future as well.

As far as mainstream languages go (I consider Scala, Rust, or Reason a border line just outside the mainstream), TypeScript could be the most interesting choice today IMO.

This was my trailer/preview section. If the code that excited me feels interesting to you, you may enjoy reading these notes. There will be some gory details (not a lot violence). You have to decide if type safety is your genre.
Developers are divided into 2 camps: Those who use types because that is the most effective way to write software and those who do not use types because that is the most effective way to write software. Since you are still reading, I assume you are in camp 1.

office.js. Using TS in anger

I will use office.js library as a source of examples for this series. It is a Microsoft product (like TypeScript). It comes with TypeScript type definitions (this series uses @types/office-js@1.0.221).
Looking into the office.js revision history suggests that the bond between office.js and TypeScript developed very early. It almost looks like these projects grew up together. office.js seems like a good ‘comprehensive’ example for examining the benefits (and frustrations) of using TS in anger.
Despite some hardships, TS makes working with office.js much, much easier!

As the name suggests, office.js provides an API for working with Microsoft Office. It allows implementing custom apps that work inside the office suite of products (Microsoft calls these apps add-ins).
This is not an office.js tutorial but, I hope, the code should be clear to follow even if you never used office.js.

As a working example, we will play with code that extracts data from an email opened in Outlook. To start, I want to extract the email body.
To access data, office.js often uses an old style getAsync methods that I will modernize using a custom conversion to a Promise. Node’s util.promisify will not work well for this task. This is how this could be done in TS:

/* Utility to convert office functions to promises */
export const officePromise = <T> (getasync: (fx: (r: Office.AsyncResult<T>) => void) => void): Promise<T> => {
    return new Promise((resolve, reject) => {
      getasync((res: Office.AsyncResult<T>) => {
        if(res.status===Office.AsyncResultStatus.Succeeded){
          resolve(res.value)
      } else
          reject(res.error)
      })
   })
  }

Side Note1: Here is my first criticism of TS. The ergonomics of function type definitions is IMO really poor. These definitions are hard to read and cumbersome to write. This syntax does not scale well to more involved types and makes reasoning about types harder.
E.g. in the above example parameters fx: and r: cannot be used anywhere (are outside of the lexical scope) and serve only a documentation purpose. This simple example needs 6 parentheses. The use of : and => is confusing. Function form A to B is (depending where in the declaration) either (a: A) => B or (a: A): B. I admit it took me a long time to figure out how to write these and it still takes me forever to read some of these types.
Later in this post, I will show some work-arounds that simplify type definitions like this one.
I am adding a big fat IMO to this side note, readability is in the eye of … well the reader. But seriously…

Properly initialized office add-in will have access to Office.context.mailbox.item: Office.MessageRead.
This item object allows access to the email data.2 To retrieve the email body I need to use item.body.getAsync. But wait, the type for that version of getAsync accepts not only a callback function but also a “body type” parameter.

I am going to resist the temptation to overload officePromise. Instead I will move in a direction that is more fundamental.

Assume that we want ‘html’ body format, the code can look something like this:

//retrieving email body, 1st attempt
const bodyType = Office.CoercionType.Html
 
const partiallyAppliedBodyFn = (fn: ((res: Office.AsyncResult<string>) => void)) => 
     item.body.getAsync(bodyType, fn) 
  
const body  = await officePromise<string> (partiallyAppliedBodyFn) // body: string

I had to fully specify the partiallyAppliedBodyFn type for this to work. That looks like a lot of code to just partially apply item.body.getAsync!

Happy path

There are some libraries that offer a curry function conversion, but these are typically JS not TS. So I wrote it myself (again, note the type signature is somewhat hard to read):

export const curry = <T1, T2, R> (fn: (ax: T1, bx: T2) => R): (a: T1) => (b: T2) => R => {
    const res = (a: T1) => (b: T2) => fn(a, b)
    return res
 }

const addtst = (a:number, b: number) => a + b
const curriedAdd = curry(addtst) //const curriedAdd: (a: number) => (b: number) => number
const tst1 = curry(addtst)(1) //const tst1: (b: number) => number
const tst12 = curry(addtst)(1)(2) //tst12 = 3

And I have a much simpler code that compiles right off the bat:

//Happy path one liner to get email body
//body2: string
const body2 = await officePromise (curry(item.body.getAsync)(Office.CoercionType.Html)) 

This worked out quite well and the type checker was able to infer the types!
This ended up being a happy path.

Bumps on the path

In practice, the type checker will often need some help. Even more often, the programmer (me) will need help figuring why the code is not compiling.

I will start by presenting code that should compile but it does not.

item.body.getAsync offers a 3 parameter overload which accepts additional Office.AsyncContextOptions. Using it is much harder. (I will not delve into what the extra argument is for, I just want to see if my code will compile with 3 parameters)

//boilerplate 'curry3' implementation is not shown (available in the linked github project), 
//it is almost identical to `curry` but accepts a 3 parameter function  

//trying to pass extra parameter to body.getAsync
const emptyConfig: Office.AsyncContextOptions = {}
//Compilation Error: 
//"Argument of type 'AsyncContextOptions' is not assignable to parameter of type '(asyncResult: AsyncResult<string>) => void'." 
const body3  = await officePromise (curry3(item.body.getAsync)(Office.CoercionType.Html)(emptyConfig)) 

To understand what is happening, I sometimes need to spend time annotating things, or picking up the exact overload I want. E.g.

const useThisAsync = (coercionType: Office.CoercionType
                     , options: Office.AsyncContextOptions
                     , callback: (asyncResult: Office.AsyncResult<string>) => void): void => {
      item.body.getAsync(coercionType, options, callback)
    }

This can be tedious, but it typically gets the job done. In this particular case, using curry3(useThisAsync) fixes the body3 (or the “3 body”, I just had to pun this) problem. So, the issue with body3 code appears to be related to overloading.

Looking closer at the types, I notice that not only item.body.getAsync has two overloads, but the one I want is accepting a union type argument and the callback is optional:

//from office.js documentation

//2 parameter overload used in happy path
getAsync(coercionType: Office.CoercionType | string
  , callback?: (asyncResult: Office.AsyncResult<string>) => void): void;

//3 parameter overload we are trying to use now
getAsync(coercionType: Office.CoercionType | string, 
        options: Office.AsyncContextOptions, 
        callback?: (asyncResult: Office.AsyncResult<string>) => void): void;

So there are sort of overloads on top of overloads and the type checker could get confused. In fact the case here is much simpler, TS inference tends to pick the last defined overload (see this code example and (#43187). The compilation error also suggests that the compiler gets stuck on a wrong (the 2 parameter) version of getAsync despite the use of the 3 parameter curry3. I will also confirm this hypothesis using a type hole (we will learn what that is) in the next section.
I expect the type checker to backtrack and try the next overload, but for some reason it does not want to do that on its own.
I do not blame TS, overloading gives me a headache too.
Overloading is known for being not type inference friendly (incidentally, that is the reason why Haskell does not overload names).

There is something worryingly asymmetric about a 2 parameter overload compiling without additional help and a 3 parameter overload needing a developer intervention. Should I worry3 that the 2 parameter overload will stop compiling in the future? How stable is this arbitrary complexity?

If you are an API owner, my advice is to not overload. IntelliSense works better, type inference works better, developer head hurts less without overloads.

One type that is notorious for needing annotations is the TypeScript’s tuple. Typescript overloads array syntax [] to define tuples (some readers may prefer the term heterogeneous lists). This is an example of a tuple: [2,"two"]: [number, string]. The syntax overloading probably does not help TS in inferring the type and the type checker often gives up or infers the array type.

I am concerned that many developers will give up trying to write this type of code. My concern is also that developers will resort to unsafe type coercion / type casting. There will be a lot of myvar as IWantIt, or a lot of the any type.

Side note: I can push this code to a ridiculous limit and demonstrate the first example of code compiles but I would not expect it to:

//this compiles by using a wrong input parameter type and returns 'body4: unknown'
const crazyConfig : (_: Office.AsyncResult<string>) => void = x => ""
const body4 = await officePromise (curry3(item.body.getAsync)(Office.CoercionType.Html)(crazyConfig)) 

Accepting invalid unknown appears to be a common pattern to how TS sometimes works. We will come back to this example later in this post and we will discuss the unknown problem more in future notes.

Was this enough gore for you? You say it was not? I say you did not see the content of that email!

Bump leveling tools

Readable Type Definitions

Cumbersome type annotations are not a good excuse to give up! There is a way to simplify function type definitions. For example, I can define a helper alias:

//DIY reusable type for Office getAsync callbacks
export type OfficeCallack<T> = (_: Office.AsyncResult<T>) => void

Here is how this simplifies the previously defined partiallyAppliedBodyFn:

//before:
const partiallyAppliedBodyFn1 = (fn: ((res: Office.AsyncResult<string>) => void)) => item.body.getAsync(Office.CoercionType.Html, fn) 
//after:
const partiallyAppliedBodyFn2 = (fn: OfficeCallack<string>) => item.body.getAsync(Office.CoercionType.Html, fn)

Notice no more redundant parameter definitions in the type signature and a much easier to read syntax.
The next version is my personal preference (it nicely separates the type and the implementation)4:

const partiallyAppliedBodyFn3: (_: OfficeCallack<string>) => void = 
  fn => item.body.getAsync(Office.CoercionType.Html, fn)

Type Application

Returning to my failed body3 example, instead of trying to type annotate with full type signatures, it is sometimes more convenient to apply the types. Here, I have the “generic” (or polymorphic) curry3 function that I can apply the types CoercionType, AsyncContextOptions, OfficeCallack<string>, and void to:

//type applied version, it just compiles!
const emptyConfig: Office.AsyncContextOptions = {}
const body3  = await officePromise<string> (
  curry3<Office.CoercionType, Office.AsyncContextOptions, OfficeCallack<string>, void> //explicity specified type parameters
     (item.body.getAsync)
     (Office.CoercionType.Html)
     (emptyConfig)
  ) 

That is so much easier than specifying the exact useThisAsync overload!

Type Holes

A DIY type hole technique is sometimes useful to help figure out stubborn types (see Type holes in TS).

//genric (why not say polymorphic) bottom function will allow me to ask type questions
export const _ = <T>(): T => {
    throw new Error("hole"); 
}

A type hole allows me to ask the compiler type questions.
You can learn a lot about how the type checker works using it. E.g. using my Either type as an example:

const tstnum: Either<number, string> = {type: "left", content: _()}

if you hover over _ you will see

(alias) _<number>(): number

Nice! If you hover over _ in this expression

const str = "Hello " + _()

you will see

(alias) _<unknown>(): unknown

This can provide a lot of insight into types and how TS uses them!

I have not been very lucky in using type holes to figure out why TS is confused. Returning to my failed body3 example:

//body3 inferred type is 'unknown'
const body3  = await officePromise (curry3 (item.body.getAsync)(Office.CoercionType.Html)(_())) 

if I hover over the _ function, the IntelliSense suggests this completely wrong type:

(alias) _<((asyncResult: Office.AsyncResult<string>) => void) | undefined>(): ((asyncResult: Office.AsyncResult<string>) => void) | undefined

The type hole confirms that the compiler is trying to match against the two parameter overload of item.body.getAsync. This confirms my hypothesis from the last section that what made TS confused here was the overloading. There are a few things to note here:

  • We are asking TS “why are you confused?” and that is a funny question.
  • This type hole did not tell us more than the compilation error message itself. However, the type hole is more targeted so it could reveal more specific information in some cases.
  • Type holes may tell us something useful in situations where the code compiles but we do not understand why.

If, as before, I add the type application (<Office.CoercionType, Office.AsyncContextOptions, OfficeCallack<string>, void>) to curry3 the _() will show the type correctly:

(alias) _<Office.AsyncContextOptions>(): Office.AsyncContextOptions

About some limitations
Sadly, the _<T>(): T is not universally useful, e.g. this will not compile:

//compilation error: 
//       Argument of type '(ax: never, bx: never) => never' is not assignable 
//       to parameter of type '(ax: unknown, bx: unknown) => unknown'.
const testfn = curry(_()) 
//interestingly the following compiles as curry<unknown, unknown, unknown>
const testfn = curry({} as any)

TS type checker does not work with type variables at the top level. That makes testing expressions like curry(_()) rather pointless. The following compiles just fine, and would be a good (not very useful but good) choice for the inferred type of _():

type GenFn2Type = (ax: never, bx: never) => unknown

const compiles = curry(_<GenFn2Type>()) 

but TS fails instead of inferring that type.

There is an interesting relationship between the never type and _<T>(): T. There will be a future note about it.
The type hole _ function is a useful tool and we will keep using it in future type explorations.

Using types requires some experience, knowledge, and patience. More advanced types come with more misleading error messages, it takes experience to find the underlying cause of a misleading compilation error, and that is true in any language. Eventually, I (and you) will look at a TS compilation error and will say “ah, you really meant this: …”.

I am mostly left to my own devices when working with more involved types in TS. Hopefully the future will bring us mainstream grade interactive tools that allow asking type questions, browsing types, and help solving type puzzles. For now it is mostly the programmer who connects the dots.
The good news is that this gets easier and easier with practice. I have been working in TS for only about 2 months now and I already see a difference.

Good code requires two type checkers: TypeScript and You

Compilation bloopers

We already saw “correct” programs that should have compiled but did not (e.g. curry(_()), body3 example) and we will see more in the future notes. Our body4 example compiled but it was a bug.
This note shows other, less contrived, examples that compile and are clearly bugs.

All of these type check:

//annotated correct code added for reference, this code compiles
const good: (a: Office.CoercionType) 
          => (b: ((asyncResult: Office.AsyncResult<string>) => void)) 
          => void
    = curry (item.body.getAsync)

//compiles but it should not, compiles even with type annotation
const nonsense1: (a: Office.CoercionType) 
          => (b: ((asyncResult: Office.AsyncResult<string>) => void)) 
          => void
    = curry (curry (item.body.getAsync)) 

//compiles but it should not
const nonsense2 = curry(curry)

//... more examples in the linked github project

and all, except the first one, are bugs.
One pattern is clearly visible: unknown somewhere in the type5.
The underlying reason seems to be much simpler: TS does not exactly match the types of parameters that are functions. The underlying reason is that functions with fewer parameters are assignable to functions that take more parameters6:

declare function testfn(fn: (str:string) => number):number

//compiles, calculated type is: const num: number
const num = testfn(() => 1)

IMO this language design decision can lead to very confusing escaped bugs and it smells like subtyping7. Higher order functions are not uncommon in JavaScript. The nonsense1 example is a piece of code I accidentally wrote in my project.
This is very concerning since errors like these are likely to remain uncaught and become escaped bugs.
Careful reader will notice that my body4 example is a perfect storm. Here it is again:

//this compiles by using a wrong input parameter type and returns 'body4: unknown'
const crazyConfig : (_: Office.AsyncResult<string>) => void = x => ""
const body4 = await officePromise (curry3(item.body.getAsync)(Office.CoercionType.Html)(crazyConfig)) 

TS picks a (wrong) 2 parameter overload of item.body.getAsync because it was defined last by office.js. It assigns it to curry3 because curry3 expects a 3 parameter function and 2 < 3 is OK.
Sadly, accepting body4 code is TypeScript “Working as Intended” (#43187, #48624).

Compared to other programming languages I use, TS’s rate of compiler issues I encounter is much higher, the issues are more dangerous, and are likely to happen on more commonly used vanilla code (well… at least commonly used by me).
I can see two general reasons for this: gradual typing on top of JS is not easy, subtyping is not easy. I plan to write a note about the complexity of TS types in a future post.

It’s all worth it

One common concern related to using types (especially more advanced types) is a slowdown in the development speed.
There is some truth to this in general because of things like compilation times in some language environments. I cannot comment on TS compilation times for large projects, so far it is not a problem for me. In my experience, having a type checker is a huge productivity bust. In my experience, the more types the faster the development speed. That is true even with compilation bloopers.
Efficiency considerations are somewhat personal so your experience may vary.

I rewrote some legacy code using the techniques in this section. That effort resulted in significant size reduction and an overall big improvement in readability and correctness when compared to the code I was replacing or to code in the office.js documentation.
A lot of the improvement comes from using await async syntax sugar but converting functions to their curried form and figuring out more terse ways to type annotate also results in added clarity and significant syntactic simplification.

In my book, there is just no comparing TS to JS, TS is the clear winner.
How does TS compare to statically type checked frontend languages that compile to JS and have capable type checkers and solid types (e.g. Reason, Elm, PureScript, even Haskell)? I am not in a good position to discuss this yet.
Lots of projects need to stay close to JS, my project at work falls into this group. For such projects TS is the right choice IMO.

Relevant TypeScript Language tickets

  • #43187 the overloading issue (type inference considers the last overload only) has been known and has been marked as “Docs”.
  • #48624 (I entered it) about my blooper examples has been marked as “Working as Intended”
  • #48625 curry(_()) not compiling issue (I entered it) has been marked as “Working as Intended”

Next Chapter

We are not done with office.js. I will use it in future notes.

Do statically defined types reflect the actual runtime values? How to assure that they do?
We will discuss these questions in the next installment. Here is the link: Part 2. Typing Honestly

Summary of final edits

Added context to bumps on the path section about type inference not working well with overloaded methods.

Added context to why curry(_()) is not compiling.

Added context to compilation bloopers section explaining the underlying reason for TS accepting my blooper examples: TS allows to assign a function with fewer parameters to a function type with more parameters. The arity does not need to match.


  1. I rewrote the side note and improved officePromise type definition based on a comment from u/Tubthumper8 on reddit. Thanks!↩︎

  2. The situation is just slightly more complicated since the item property is overloaded but that is not important for now.↩︎

  3. As the previously linked example shows, I indeed should worry.↩︎

  4. It should be noted that with this syntax type variables will be defined in the lhs of the definition and will be outside of the lexical scope in the rhs. You would have to re-declare them which makes this approach much less usable in the presence of type variables.↩︎

  5. nonsense1 will will show unknown if you remove the type annotation.↩︎

  6. This took me a long time to figure out and was added late.↩︎

  7. In fact it is subtyping. In TS () => number extends (_:string) => number. One can argue that this TS design decision follows from how JS works and is often used.↩︎