Mutable vs Immutable State
Traditional TypeScript
// Mutation everywhere
let count = 0;
function increment() {
count++; // Side effect!
console.log(count); // Another side effect!
return count;
}
// What if called from multiple places?
// What if count is modified elsewhere?
// How do you test this?
Issues
- ❌ Hidden mutation makes testing hard
- ❌ Side effects not tracked in types
- ❌ No guarantee of execution order
- ❌ Cannot safely parallelize
- ❌ Debugging requires tracing all mutations
→
typelang
// Pure transformation
const increment = () =>
seq()
.let(() => State.get<{count: number}>())
.let((state) => ({count: state.count + 1}))
.do((next) => Console.op.log(`${next.count}`))
.do((next) => State.put(next))
.return((next) => next.count);
// Type: Eff<number, State | Console>
// Test: swap State handler for mock
// Debug: all state changes explicit
Benefits
- ✅ All effects tracked in type signature
- ✅ Pure functions = easy testing
- ✅ Explicit sequencing with seq()
- ✅ Handler swapping for different contexts
- ✅ Time-travel debugging possible