The Case for Composable Notations (5 of 5) UNIX Already Showed Us the Way
2026-03-30
TL;DR
UNIX demonstrated that programs composed from small, single-purpose tools connected by pipelines are more flexible than monolithic programs that try to do everything. The same principle applies to programming notations. We don’t need to start over. We need to start composing.
L;R
The previous articles in this series have been largely critical. FP imposes restrictions. State has been hidden rather than expressed. Ease of expression has been sacrificed for notational monoculture. The natural question is: what do we do instead?
The answer is not to push the big red button and start over. The answer is already partially in front of us. UNIX demonstrated it fifty years ago. We just haven’t finished reading the lesson.
What UNIX actually showed us.
The UNIX philosophy was built around a small number of powerful ideas. Programs should do one thing well. Programs should produce output that can become the input of another program. Programs should be composable.
The mechanism was text. UNIX chose plain text as the universal interface between programs. Not because text is the best possible representation for every kind of data — it isn’t — but because text is the one representation that every tool can read and write without negotiation. Text is the lowest common denominator that makes composition possible.
The result was pipelines. A sequence of small programs, each transforming text in one specific way, connected so that the output of one becomes the input of the next. No single program in the pipeline needs to know what the others do. Each program sees text in, produces text out, and minds its own business.
This is not just a technique for manipulating documents. It is a model for composing notations.
Source code is text.
Source code is text. This is obvious but underexploited. If source code is text, then the UNIX pipeline model applies directly: a program that reads source code in one notation and produces source code in another notation is a text-to-text transmogrifier — a t2t tool. Chain enough of them together and you have a pipeline that accepts source in one notation and delivers executable behaviour, passing through whatever intermediate notations are useful along the way.
This is not a new idea. It is how most compilers work internally — a sequence of transformations from source text through intermediate representations to machine code. The insight is to make the intermediate stages explicit, accessible, and composable at the level of the programming workflow rather than buried inside a single tool.
When you do that, each stage in the pipeline can use a different notation. A diagram tool produces a structured description of component topology. A t2t transmogrifier converts that description into a skeleton in a general-purpose language. Another transmogrifier fills in behaviour from a domain-specific notation. Another checks the result against constraints expressed in a relational notation. The final stage produces code in whatever target language the runtime expects.
No single notation does everything. Each notation does one thing, in its sweet spot, and hands off to the next.
Little languages over GPLs.
General-purpose programming languages are an answer to the question: what if we had to pick one notation for everything? The answer is inevitably a compromise. A GPL must be expressive enough to handle every problem domain, which means it cannot be optimally expressive for any particular one.
Little languages — small, purpose-built notations for specific problem domains — make the opposite trade. A little language for describing component connections does not need to handle string manipulation or arithmetic. A little language for expressing state transitions does not need to handle network I/O. Each little language is narrow, and narrow is what makes it clear.
BNF is a little language for describing grammars. It has been enormously productive precisely because it is narrow — it does one thing and does it with a clarity that no GPL-based alternative matches. The programmer says what the grammar is, not how to parse it. The notation carries the intent directly.
The objection is usually: little languages are expensive to build. This was true when language tools were scarce and difficult to use. It is much less true now. Parser generators, PEG libraries, macro systems, and pattern-matching tools have made the construction of small, purpose-built notations a realistic part of a programming workflow rather than a research project. The cost argument has not kept pace with the tooling.
Existing languages as assembly languages.
We do not need to abandon the languages we have. We need to change their role.
A general-purpose language — Python, JavaScript, Rust, whatever the current choice is — can serve as an assembly language for a higher-level notation. The higher-level notation expresses the problem in its natural terms. The t2t transmogrifier converts that expression into the GPL. The GPL handles the runtime details. The programmer works at the higher level and does not need to think about the GPL except when debugging the transmogrifier.
This is not theoretical. It is how most templating systems work. It is how CSS preprocessors work. It is how build systems with declarative configuration work. The pattern is familiar. The step that remains is to generalise it — to treat the composition of notations as a first-class part of programming practice rather than a specialised technique used in specific tool-building contexts.
Composing paradigms.
The functional paradigm has a sweet spot. The relational paradigm has a sweet spot. State machine notations have a sweet spot. Diagrammatic notations have a sweet spot. None of these sweet spots covers the full range of problems that real software systems contain.
A system composed from multiple notations, each handling the part of the problem it fits best, connected by t2t transmogrifiers into a coherent pipeline, is not more complex than a system forced into a single notation. It is less complex — because the notation at each stage is close to the problem at that stage, and proximity between notation and problem is where complexity goes to die.
The composition layer — the pipeline — does not need to be sophisticated. UNIX demonstrated that plain text and simple conventions are sufficient. The sophistication lives in the individual tools, where it belongs, not in the connection mechanism.
PBP as a current experiment.
Parts Based Programming — PBP — is an ongoing experiment in this direction. The core idea is that software components should be isolated, asynchronous by default, and connected by explicit routing rather than direct function calls — modelled on how hardware components behave rather than how mathematical functions compose.
The toolchain is a pipeline of t2t transmogrifiers. Diagrams produced in draw.io describe component topology. A transmogrifier converts those diagrams to a structured representation. Further transmogrifiers convert that representation into runnable code. Each stage uses the notation appropriate to its task.
The experiment is not finished. It is not a product. It is a demonstration that the pipeline model works for composing notations in a software development workflow, using tools that exist today, without requiring a new runtime or a new theory of computation.
The path forward does not require abandoning what we have. It requires treating what we have as a set of components — each with its own sweet spot, each expressible in a notation suited to it — and learning to compose those components as deliberately as UNIX learned to compose programs.
The pipes are already there. We just need to start using them.
End of series.
This is article 5 of 5 in “The Case for Composable Notations.” A Part 6 of 5 follows — an appendix describing notation experiments currently in progress, with pointers to repos and an open invitation to collaborate.
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

