Type Driven Development: Indexed Types

Written by

in

Part 5 – Indexed Types

This is a series of posts I’m writing about using types as another tool in software development, Continuous Delivery, & keeping LLM’s honest. They’re also a design & refactoring tool, a communication tool, and reduce how many tests you have to write.

When we create Objects, all have the same thing in common: “property name and value”.

person = { first: 'Jesse', powerLevel: 9001 }Code language: JavaScript (javascript)

We access that person’s first property in 2 ways, either person.first, or person['first']. At runtime, accessing the first property will give us the first’s value, 'Jesse'.

Product types are usually a collection of types grouped together:

type Person = { first: string, powerLevel: 9001 }

We know how to access object values at runtime, but how do we access type property names?

We access them the same way we do objects using the name index method (e.g. person['first']), except it’s “type name and type value”:

type First = Person['first']Code language: JavaScript (javascript)

So type First is also just string. Sadly, u can’t do First.first, but if you remember “index” similar to how you access an Array’s items, like myList[2], basically the brackets [], that helps me remember.

A Money type may look like this:

type Money<C> = { currency: C, amount: number }Code language: HTML, XML (xml)

The reason you care about indexing is for some operations you want to keep safe, you need to know specific parts of the type. For Money that’d be Money['currency'].

With money, you don’t really care a/b the amounts when adding, you first ask “Are currencies the same type?” b/c you can’t just add USD (United States Dollars) + EUR (Euro)’s together. Each currency is worth a completely different amount. Converting them to & from each other, u have to know the exchange rate. It changes every second so we’ll assume u got it from somewhere.

This business rule, or “invariant”, is often baked into money libraries, like dinero.js. However, what a lot of those libraries have in common is they do not enforce those rules in the type system; instead, they go halfway then explode at runtime.

function add(a:Money, b:Money):Money {
  if(a.currency !== b.currency) {
    throw ErrorCode language: JavaScript (javascript)

Code like this assumes you’ll create the same types & ensure you don’t call add w/USD + EUR. That is not encoding the business logic in the type system. Instead, you’d do something like:

function add<C>(a:Money<C>, b:Money<C>):Money<C> {Code language: HTML, XML (xml)

Notice there’s only one C, or currency type parameter there, not two; so that means u HAVE to pass the same currency to add, USD or EUR. There are additional nuances around TypeScript & other languages inferring C is a Union so it could allow USD; use the NoInfer type for the 2nd parameter, but u get the point: types enforce the business rule. Indexing gives you ability query & refine the types when u’re working w/2 Product Types + u need to know details abt how to combine them. Math w/Money, math w/ temperatures & measurement (Metric vs Imperial), or account migration & other mapping operations that need to be type safe.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *