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


Option/Maybe is a type to model data is not there & be a replacement for null/nil/undefined.
Tony Hoare, a British Computer Scientist who passed away back in March (bro was a Knight!) is credited for inventing null. The idea of null was “How do I represent something that isn’t there?” The issue was implementations of this idea caused computers to crash. A lot. Dynamic languages like JavaScript had their crashes far away from where the null entered the system making it harder to debug.
In the 1970’s Meta Language crew created an Option type (think of it like a Result):
type Option<T> = Some | None
type Some<T> = { tag: 'Some', data: T }
type None = { tag: 'None' }Code language: HTML, XML (xml)
Created as a Discriminated Union this ensured all null’s were forced to be handled before you could compile. This effectively “fixed” Tony’s “1 billion dollar mistake”. Other languages over the decades have implemented their own version of this, like Haskell calling it Maybe (e.g. Just<T>, Nothing) w/Monad powers.
Modern language type systems fixed some of their type lies, like Java saying “it’s a string… but jk, it can be null too”.
String greeting = "Hello, World!";
// in older Java, was basically string | nullCode language: JavaScript (javascript)
Even Dart did this, effectively removing null pointers. TypeScript, Python, and Luau arrived at something similiar when they implemented unions, and those unions were allowed to be anonymously defined:
type ReadEnvironmentVariable = (name:string) =>
string | undefinedCode language: JavaScript (javascript)
Notice string | undefined isn’t defined anywhere; it’s just an anonymous type you defined. You could name it (which I encourage):
type EnvironmentVariableMaybe = string | undefinedCode language: JavaScript (javascript)
But because it was _so easy_ to define Maybe types like that, and TypeScript implemented a strict option, you started seeing null pointers just disappear except in places where developers intentionally turned off the type system (casting using as, using any, etc).
Using Maybe follows a Gartner Hype cycle: you learn about, realize all null pointers never happen anymore (no undefined is not a function). Then you realize “oh gawd, I have to provide defaults, and handle all these unhappy paths”. So you then swear them off, never allowing optional values. Then you realize things like environment variables _have_ to be optional. Finally, you realize Maybe/Option types are a lot like side-effects in Functional Core, Imperative Shell style architecture, like side-effects to be pushed to the edges of your code base, clearly indicating danger areas to be modeled.
Modern FP languages like Roc don’t even include it. It was common to model null with a Union type of “data or null” called Option or Maybe. You’ll learn to avoid if possible as it’s better to use a Result.
Leave a Reply