The Case for Composable Notations (2 of 5) The Restrictions That Came With the Cow
2026-03-30
TL;DR
Functional programming imposes a specific set of restrictions. Each one is a notation choice. None of them are laws of nature.
L;R
Every notation has a sweet spot — a class of problems it expresses clearly and cheaply. Outside that sweet spot, the notation fights you. The restrictions are not bugs; they are the shape of the cow. Understanding them precisely is the first step toward knowing when to put the cow down.
Here is what FP imposes.
Single-thread thinking.
FP is fundamentally sequential. What we call “multi-tasking” today is time-sharing — a technique invented to fake concurrency by splitting a single thread into separate state machines, saving their state in hidden data structures, and switching between them at boundaries the software architect does not control. The operating system manages the illusion. The programmer works inside it. Real concurrency — multiple things happening independently, with no shared thread — is not expressible in the functional notation without leaving it.
One in, one out.
A function takes arguments and returns a value. One bundle in, one bundle out. stderr is a workaround for the cases where you need a second output channel. Exceptions are a workaround for the cases where returning a value is the wrong response to a situation. Both are bolt-on mechanisms grafted onto a notation that does not natively support multiple outputs. The workarounds work — but they are workarounds, and their seams show.
Elision of mutation.
FP treats assignment as suspect. Mutation is discouraged, hidden behind monads or immutable data structures, or banned outright. This is useful for a certain class of problems. It is not a description of how computers actually work. CPUs mutate state. Memory is mutable. Pretending otherwise requires the runtime to maintain an increasingly elaborate fiction — and pushes the mutation somewhere the programmer cannot see or control it.
Fixed control flow.
FP offers one control flow structure: top to bottom, left to right, as inherited from the printing press. Conditionals and recursion are permitted variations within that structure. Anything else — event-driven flow, concurrent flow, flow driven by external signals — requires leaving the notation or bolting something onto it. The notation does not make other control flows easy to express. It makes them feel unnatural, because the notation itself has no vocabulary for them.
Routing.
In a functional program, routing is sequential by default. Data moves from caller to callee and back. Expressing any other routing topology — broadcast, fan-out, selective dispatch, feedback — requires constructing it manually from the available primitives. The notation does not help. Every non-sequential routing pattern is an exercise in working around a notation that was not designed to express it.
Synchrony.
Every function call is synchronous. The caller suspends. The callee runs. The caller resumes when the callee returns. This is a specific, narrow model of coordination — useful for calculator-like computation, costly for anything else. Event-driven systems, device drivers, network protocols, user interfaces, robotics — these domains are asynchronous by nature. Forcing them into a synchronous notation produces the bolt-ons: callbacks, promises, async/await, reactive frameworks. The notation keeps winning. The domain keeps losing.
Evaluation order as a hidden assumption.
FP inherits a specific evaluation model and mostly keeps it invisible. Some languages make it explicit — Clojure, for instance, specifies evaluation behaviour in certain contexts — but the mechanism for doing so is itself a notation-level workaround rather than a first-class tool. When you need to control the order in which things are evaluated, you should be able to say so clearly. Instead, you learn which incantations coerce the runtime into the order you need.
What this adds up to.
Each of these restrictions was a reasonable choice in 1960. CPUs were expensive, memory was scarce, and mathematical function application was a clean, verifiable model for a narrow class of computation. The restrictions made the cow spherical on purpose.
The problem is not the restrictions. The problem is that we have spent decades adding baubles — monads, continuations, effect systems, async layers, evaluation order rules — to stretch the notation past its sweet spot, rather than acknowledging that different problems need different notations.
We don’t still describe computers in terms of transistors. We invented higher-level notations because ease of expression matters. The same logic applies here.
Next: State Isn’t the Enemy.
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

