The Case for Composable Notations (1 of 5) The Spherical Cow We Forgot We Were Riding
2026-03-26
TL;DR
McCarthy invented functional programming as a notation convenience — a spherical cow. The programming community forgot it was a cow. Decades of workarounds followed.
L;R
Physics has a tradition of inventing simplified notations to make hard problems tractable. A spherical cow is a deliberate lie — a simplification that strips away inconvenient reality so you can think clearly. The rule is: remember it’s a lie.
McCarthy invented Lisp 1.5 under the same spirit. CPUs and memory were expensive. A notation that treated computation as pure mathematical function application — no side effects, no time, no messiness — was a useful fiction. It let you reason cleanly about certain classes of problems.
The rule applies: remember it’s a fiction.
The programming community forgot.
C smudged the boundary.
Before C, the distinction was explicit: subroutines and functions were different things. A subroutine can have side effects. A function — by definition — cannot. A function with zero arguments that returns a value must return the same value every time, or it isn’t a function; it’s a subroutine wearing a function’s clothing.
After C, everything became a “function.” The word lost its meaning. Today most programmers treat a zero-argument procedure with side effects as a function. It isn’t. The confusion isn’t cosmetic — it shapes how an entire generation thinks about what programs are.
What got lost.
McCarthy’s original eval/apply engine was small and tight. That engine made sense for its era and its cost constraints. CPUs were expensive. Memory was expensive. The fiction paid for itself.
CPUs and memory are now effectively free. The original justification evaporated. What remained was the notation, now mistaken for ground truth.
The cost of maintaining the fiction has compounded. To allow “functions” to interact with the real world — which is irreducibly stateful and asynchronous — the community has had to invent a growing stack of workarounds: monads, continuations, effect systems, callback protocols, async/await layers, and more. Each workaround is a patch over the mismatch between the spherical cow and reality. Each patch makes the next patch harder to avoid.
The deeper confusion: data flow vs. control flow.
Functional programming is often described as “data flow.” It isn’t.
When a caller passes data to a callee in a functional call, two things happen simultaneously: data moves, and control transfers. The caller suspends. It waits. The callee must process the data immediately and return before the caller can proceed. That is synchronous control flow with data attached — not data flow.
Real data flow is asynchronous. You send data to a receiver. The receiver processes it in its own time. The sender does not wait. The sender does not care when processing happens. The sender does not even need to know that processing happened.
Functional programming cannot express this without leaving the paradigm. The workarounds (callbacks, futures, reactive streams) are all ways of bolting asynchrony onto a fundamentally synchronous notation. The bolt-on is visible everywhere — callback hell being only the most obvious symptom.
The notation is not the only notation.
BNF exists. It describes what to parse without specifying how to parse it. Most programmers, though, would rather write low-level parsing code in a functional style than reach for BNF, even when BNF is clearer, shorter, and more correct. The functional notation has become so dominant that alternatives feel exotic.
Prolog exists. Its relational engine is genuinely different — it lets you query a factbase in a notation suited to relational problems. Most programmers never reach for it, even when the problem is relational. Instead, they twist a functional language to approximate what Prolog does natively, at greater length and with more bugs.
Harel’s Statecharts exist. They describe stateful, reactive, hierarchical behaviour in a notation built for that purpose. Most programmers implement state machines as switch statements in a functional language instead.
The alternatives did not lose on merit. They lost because the functional notation achieved cultural dominance and crowded out the vocabulary needed to even ask whether a different notation might fit better.
The actual problem.
Most programmers now believe that programming is writing machine code for a CPU. Not designing systems. Not composing components. Not choosing a notation suited to a problem domain. Writing CPU instructions — with a functional veneer on top.
This is the wrong spherical cow. It was never meant to be the whole of programming. It was a notation for a particular class of problems, invented under particular cost constraints, in a particular era.
The constraints are gone. The notation stayed. The forgetting was the mistake.
See Also
Email: ptcomputingsimplicity@gmail.com
Substack: paultarvydas.substack.com
Videos: https://www.youtube.com/@programmingsimplicity2980
Discord: https://discord.gg/65YZUh6Jpq
Leanpub: [WIP] https://leanpub.com/u/paul-tarvydas
Twitter: @paul_tarvydas
Bluesky: @paultarvydas.bsky.social
Mastodon: @paultarvydas
(earlier) Blog: guitarvydas.github.io
References: https://guitarvydas.github.io/2024/01/06/References.html

