Founder of SAGEWORKS AI — building the Web4 layer where AI, blockchain & time flow as one. Creator of Mind’s Eye and BinFlow. Engineering the future of temporal, network-native intelligence.
The comparison across languages is useful, but the thing I find myself thinking about is whether "make illegal states unrepresentable" is always the right goal in the first place—or whether it's a tool that comes with its own cost.
There's a quiet assumption in this approach that the state machine is known and stable at the time you're writing the types. For a pizza builder, sure. The rules about pineapple and cream base aren't changing next sprint. But I've worked on enough systems where the business rules were in flux—where "potatoes are only compatible with cream base" would last exactly until a customer success manager promised a client they could have potatoes on anything—that I've started to get nervous when I see a type hierarchy that perfectly encodes today's constraints.
The thing is, when you encode rules in types, changing a rule stops being a one-line validation change and becomes a refactoring exercise that ripples through every call site. Sometimes that's worth it. Runtime validation is cheaper to change but more expensive over time because it fails late, at the wrong moment, in production. Static enforcement fails early but is rigid. You're trading flexibility for safety, and the tradeoff looks different depending on how far the code is from the customer's changing mind.
I've started asking myself a question when I reach for phantom types or sealed interfaces: "Am I encoding a law of the domain, or am I encoding a preference that someone might negotiate away next quarter?" The laws deserve types. The preferences might deserve a comment and a runtime check.
Have you found yourself ever removing a phantom type constraint because the domain shifted under it? Or do you find that once you've locked things down at the type level, the domain tends to stabilize around the types?
Let me widen your point. When I was a young engineer, I told to always write a parent interface and abstract class to every class I built, so we could change the design "in the future". Guess what? The future never happened, and we had an overengineered design. Please See Are you guilty of over-engineering? for my whole argument.
Yes, the customer may change their mind. But they might as well not. Or they don't have budget. Or they might have other priorities. Etc. Nowadays, apart from very obvious cases that are indeed context-dependent, I design for the now.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
The comparison across languages is useful, but the thing I find myself thinking about is whether "make illegal states unrepresentable" is always the right goal in the first place—or whether it's a tool that comes with its own cost.
There's a quiet assumption in this approach that the state machine is known and stable at the time you're writing the types. For a pizza builder, sure. The rules about pineapple and cream base aren't changing next sprint. But I've worked on enough systems where the business rules were in flux—where "potatoes are only compatible with cream base" would last exactly until a customer success manager promised a client they could have potatoes on anything—that I've started to get nervous when I see a type hierarchy that perfectly encodes today's constraints.
The thing is, when you encode rules in types, changing a rule stops being a one-line validation change and becomes a refactoring exercise that ripples through every call site. Sometimes that's worth it. Runtime validation is cheaper to change but more expensive over time because it fails late, at the wrong moment, in production. Static enforcement fails early but is rigid. You're trading flexibility for safety, and the tradeoff looks different depending on how far the code is from the customer's changing mind.
I've started asking myself a question when I reach for phantom types or sealed interfaces: "Am I encoding a law of the domain, or am I encoding a preference that someone might negotiate away next quarter?" The laws deserve types. The preferences might deserve a comment and a runtime check.
Have you found yourself ever removing a phantom type constraint because the domain shifted under it? Or do you find that once you've locked things down at the type level, the domain tends to stabilize around the types?
Really interesting comment.
Let me widen your point. When I was a young engineer, I told to always write a parent interface and abstract class to every class I built, so we could change the design "in the future". Guess what? The future never happened, and we had an overengineered design. Please See Are you guilty of over-engineering? for my whole argument.
Yes, the customer may change their mind. But they might as well not. Or they don't have budget. Or they might have other priorities. Etc. Nowadays, apart from very obvious cases that are indeed context-dependent, I design for the now.