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.

Parts:
  1. Part 1 – Branded Types
  2. Part 2 – Product Types
  3. Part 3 – Union & Discriminated Unions
  4. Part 4 – Non-Empty Collections
  5. Part 5 – Indexed Types
  6. Part 6 – unknown vs any
  7. Part 7 – Result
  8. Part 8 – Schema
  9. Part 9 – Total Function
  10. Part 10 – Errors as Values
  11. Part 11 – Property Tests
  12. Part 12 – Type Proofs
  13. Part 13 – Exhaustiveness Checking
  14. Part 14 – Parse, Don’t Validate
  15. Part 15 – Anti-Corruption Layer
  16. Part 16 – Opaque Types
  17. Part 17 – Maybe
  18. Part 18 – Smart Constructors
  19. Part 19 – Pipeline
  20. Part 20 – Railway Oriented Programming
  21. Part 21 – Typestate
  22. Part 22 – Capabilities
  23. Part 23 – Immutability
  24. Part 24 – Making Impossible States Impossible
  25. Part 25 – Type Driven Development: How to do it
  26. Part 26 – Final Thoughts

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 *