Type-State Pattern

A universal pattern for less wrong™ code

2024-02-28

Alex Povel

💡 Premise

  • type-state is a code pattern
  • it's about offloading to the type system as much as possible
  • computers are excellent at logic
    • that's their whole deal...
    • so why not apply that power to the very instructions we give them?

🌟 About

  • language-agnostic
  • some sample use cases
  • live demo
  • discussion!
  • links look like this

⚙ Use Case 1: Unix processes

👨‍💻 Use Case 1: Demo

⚙ Use Case 1: Summary

  • whole classes of errors disappear

  • some are still possible:

    class WaitingProcess(Process):
      @classmethod
      def from_initiating_io(cls, process: ReadyProcess) -> t.Self:
          return cls(id=process.id)
    
    • however, only need the be caught once; unlikely to occur again after, compiler helps us
  • unit testing partly obsolete

  • first-class tooling and IDE support

🎒 Interlude: types are bags

  • a type represents a set of values
    • using u32 & only 3 needed? 4 billion other ways to get it wrong
    • strings are the worst offenders, don't get me started 😠
      • "infinite cardinality"
      • no type-level structure (encoding is opaque)
  • one type == one bag
    • do you keep your 🧦 in the same drawer as your 🍴?
      • no: literally typestate_irl 👍
      • yes: get help

📨 Use Case 2: Email verification

  • imagine running a newsletter
  • sending email to unverified addresses is 🙅‍♂️
    • spam
    • costs
  • can typestate help? (spoiler: yes)

👨‍💻 Use Case 2: Demo

📨 Use Case 2: Summary

  • again, some impossible states become impossible to represent

  • structured programming is great and all, but can be mifused:

    try lick_elbow():
        if not is_elbow_licked():
            for elbow in elbows:
                if elbow.is_lickable():
                    lick(elbow) if is_monday() else make_breakfast()
            raise ElbowNotLickedError
    # ... 🙂🔫
    
  • maps to database neatly, e.g. one type per table in ORM

    • shard by verification; all unverified to slow storage?

🎒 Interlude: types are bags pt. 2

  • VerifiedEmailAddress is a tidy bag
  • can just reach into it without looking
    • all possible values of VerifiedEmailAddress are valid, not just some of the entire bag
    • after grabbing from the bag, don't need to sort (if)
  • it being found in that bag guarantees certain properties
    • enforce in constructor,
    • getters/setters/properties/...

👨‍💻 Use Case 3 - UUID: Demo?

✍ In summary, typestate

  • ... can lead to less wrong™ code
    • eradicates conditionals
    • pushes upkeep of invariants to the type system: human stoopid, compiler smart
    • absolutely no money-back guarantee tho
  • ... makes impossible states impossible to represent

https://github.com/alexpovel/effective-typestate

📚 Further reading

* ... works in most languages (OOP, FP, ...)