· 4 min read
The functional inheritance
Mainstream languages quietly inherited functional programming, and the next step is making mutation and effects visible in the types.

Functional programming never took over the industry, and at this point it no longer needs to. Something quieter already happened: it changed which parts of a program have to justify themselves. Mutation used to be the default, the thing you did without thinking about it. Now it tends to arrive marked — scoped, borrowed, or pushed out to a boundary where somebody can see it.
The history reads less like a contest between paradigms and more like an inheritance.
A second model of the program#
Early high-level languages mostly preserved the machine's story: values live in named cells, instructions update them, control moves forward. Lisp, ML, and their descendants told a different one, where you evaluate expressions and build new values instead of overwriting the old ones.
The 1977 Turing Award lecture by John Backus made the case that conventional languages were still, underneath everything, transcriptions of the von Neumann machine. Haskell later pushed the alternative all the way and made purity and explicit input and output a whole-language discipline. What you get in exchange is local reasoning: the same input gives the same output, and you can swap one expression for an equal one without hunting through the rest of the program for hidden state.
Then: separate languages
- pure functions
- immutable values
- recursion
Now: borrowed vocabulary
- lambdas
- pipelines
- pattern matching
Next: visible boundaries
- ownership
- effect types
- isolated mutation
The ideas leave home#
Judged by market share, the movement looks like it stalled. The 2025 Stack Overflow survey places Lisp, F#, and OCaml in the low single digits while JavaScript, Python, TypeScript, Java, and the C family dominate. But the survey counts labels, and the labels turn out to be the least interesting part — the dominant languages have spent years absorbing the functional vocabulary.
Oracle's Java tutorial describes lambdas as treating code as data and streams as pipelines. Rust's iterator documentation builds lazy transformations out of closures. And every TypeScript codebase I've worked in leans on map, filter, immutable updates, and discriminated unions without anyone once calling it a functional program.
Both styles now sit comfortably in the same file:
const names: string[] = [];for (const user of users) { if (user.active) names.push(user.name.trim());}names.sort();const names = users .filter((user) => user.active) .map((user) => user.name.trim()) .toSorted();I still write the loop version fairly often. A pipeline can hide allocation, and failure handling inside a long chain gets awkward, so the direct loop is sometimes the clearer tool. What changed is that mutation became a choice instead of the only grammar the language gives you.
The next boundary is effect#
The forward edge is not a stricter ban on state; it is a better account of it. Rust's ownership rules let the compiler decide who may access and mutate memory. The research language Koka records side effects in function types and can turn a functional update into in-place mutation once it proves nobody else is watching the value. OCaml's effect handlers separate an operation from the code that interprets it.
Functional programming may win by becoming too ordinary to name.
A future signature might read as much like a permission slip as a return type:
price(order) -> moneyquote(order) -> money ! { network, clock, failure }Databases, interfaces, devices, and distributed systems deal in identity and change over time, so a purely functional future was never really on the table. What we are getting instead — slowly, one language feature at a time — are programs that keep pure transformation at the center and mutation local, and that can say precisely where change happens and who is allowed to cause it.