Type Driven Development: Non-Empty Collections

Written by

in

Part 4 – Non-Empty Collections

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

Now that we have the Big 3® types covered (Branded “a thing”, Product “this AND that”, and Union “this OR that”), let’s see what you can do when you combine them with type parameters; like function parameters, except for types.

Array’s often hold critical data you need, yet have a flaw: you can’t guarantee there is anything in the Array. This makes working with Arrays’ a bit painful: you have to check first to see if your item is there, THEN use it:

// check for null
if(data[0]) {
  …
}Code language: JavaScript (javascript)

This gets worse if the Array isn’t in order:

// find it first
const maybeItem = data.find( i => i.name === 'desire' )
// then check for null
if(maybeItem) {
  …
}Code language: JavaScript (javascript)

This comes up with UI’s that have multiple Accounts, or in API’s when loading Feature Flags; the use case for both is “give me the important data first, and the remaining data, I can use if I need too later”. But the data isn’t shaped like that; it’s a list you must look inside and verify what you found isn’t null. Wouldn’t it be nice if you ALWAYS had at least the data you were looking for, and the rest of the data if you needed it?

You have 2 options: a custom Product Type or an Array that can’t be empty.

First option, make a Product Type where the first is always there, and the rest can be empty or full of items:

type AtLeast1 = { item: ?, data: Array }Code language: JavaScript (javascript)

But… what type of item is there, and full of what type of items? For that, we need a parameter to customize the type to whatever we need, so we’ll add a type parameter, usually called “T” which is short for “Type” (yes, it’s uppercased unlike TypeScript/Python/Lua/Ruby/etc function parameters). In TypeScript, Luau, and other languages, parameters are usually parentheses like (one, two), whereas type parameters use the less than and greater than symbols like .

type AtLeast1 = { item: T, data: Array<T> }Code language: HTML, XML (xml)

Using it:

type AtLeast1Account = AtLeast1

Now, you’re guaranteed to have the data you need, but the rest is there if you need it:

// ✅ good
const account1 = { 
  item: primaryAccount, 
  data: [ secondaryAccount ] 
}

// ✅ also good
const account2 = { 
  item: anotherPrimaryAccount, 
  data: [] 
}Code language: JavaScript (javascript)

The other option… define an Array where it HAS TO HAVE at least 1 item:

type NonEmptyArray = [ T, …Array[T] ]

// ❌ does not compile
const empty:NonEmptyArray<string> = []

// ✅ compiles
const hasOne:NonEmptyArray<string> = ["🐮"]Code language: PHP (php)

You can see now that you have the basic core types of Branded, Product, and Union/Discriminated Unions, once you combine with Type Parameters, you can create all kinds of types to ensure the data is in the shape of what you need and the types are easier to work with while flexible for your needs.

Comments

Leave a Reply

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