The new Instantiation Expression in Typescript

With instantiation expressions, we can now take functions and constructors and feed them type arguments directly.

The new Instantiation Expression in Typescript

TLDR

Instantiation expressions provide the ability to specify type arguments for generic functions or generic constructors without actually calling them.

Introduction

You’re building a banking application and need to write a certain withdrawMoney function that takes the currency and amount to be withdrawn as parameters.

The withdrawMoney function
The withdrawMoney function

Here’s a contrived implementation anyone could arguably come up with:

const withdrawMoney = (currency: string, amount: number) => {
  // do something 
  return { currency, amount }
}

Assuming you want the type of the currency and amount to be ultimately flexible, you may go ahead and represent the arguments with generics as follows:

const withdrawMoney = <C, A>(currency: C, amount: A) => {
  // do something 
  return { currency, amount }
}

Looking good! Heck, you may win an award at work for such fine code.

The Specificity Problem

When you have generic functions as in the example above, more often than not, you may find yourself in a situation where you need a stricter or more specific version of the same function.

For example, withdrawMoney is your generic handler for withdrawing any sum of money in any currency.

However, you may find yourself requiring a more specific version that handles tipping creators one dollar (creators deserve more).

To make a more specific function, there used to be only two options.

1. Wrap the generic function in a new function

This is straightforward.

You’d write a new function tipCreator that composes the withdrawMoney function as follows:

const tipCreator = (currency: 'USD', amount: 1) => {
  return withdrawMoney(currency, amount)
}

And of course, the return value of tipCreator is correctly typed, leveraging the type of value passed to withdrawMoney.

The tipCreator function correctly typed
The tipCreator function correctly typed

See Typescript playground.

This isn’t a bad solution.

However, it seems a bit too much. All we want is to restrict the generic type for the withdrawMoney function. Do we really need to re-invoke withdrawMoney to get the appropriate type here?

Perhaps not!

Ideally, we would prefer a solution that allows you to aliaswithdrawMoney while replacing all the generics in its signature.

2. Use an explicit type for an alias

An alternative solution would be to use an explicit type for a new function, as seen below:

const tipCreator: (currency: 'USD', amount: 1) => 
{ currency: 'USD', amount: 1 } = withdrawMoney

If you remove the type information, here’s what this is stripped down to:

const tipCreator = withdrawMoney

Essentially, an alias.

The difference is the extra explicit type.

This solution works, but writing explicit types as seen above can get unwieldy rapidly.

An ideal solution would be something close to the stripped down version above, without the fuss of a full on explicit type.

See Typescript playground.

Introducing instantiation expressions

Now that you’re familiar with the problem, here’s the solution with instantiation expressions:

// look here 👇
const tipCreator = withdrawMoney<'USD', 1>

For completeness, here’s the definition for withdrawMoney:

const withdrawMoney = <C, A>(currency: C, amount: A) => {
  // do something 
  return { currency, amount }
}

How easy!

With instantiation expressions, we can now take functions and constructors and feed them type arguments directly.

Feeding types to functions and constructors directly
Feeding types to functions and constructors directly

Remember that this also works for constructors e.g., Array, Map or Set e.g.,

type Currency = 'USD' | 'EUR' | 'GBP'

// use instantiation expressions 
const CurrencyMap = Map<string, Currency>;

// use the new alias 
const currencyMap = new CurrencyMap() 
// currencyMap will have type Map<string, Currency>

There you go!

How do I know when you use instantiation expressions?

Next time you find yourself needing a specifically typed version of a function or constructor, it may be a great time to consider instantiation expressions.

Further resources

  1. The Instantiation expressions implementation
  2. The Seven Most Stackoverflowed TS Questions (PDF)
  3. Build Strongly Typed Polymorphic React Components (PDF)