The question of how few
How small can a programming language be and still do real work? The answer is surprising: very small. Many tiny languages can, in principle, compute anything that a full-sized language can. What changes as the instruction set shrinks is not what is possible but how much effort it takes.
This is the heart of minimalism in language design. A minimal language is attractive because it is easy to specify, easy to implement and easy to reason about. It is also, almost always, unpleasant to write programs in.
An example: eight commands
The Brainfuck commands are the standard example. There are eight of them: move a pointer left or right along a row of cells, add or subtract one from the current cell, read or write a character, and begin or end a loop. That is the whole language.
Here is a short fragment that moves the value in the first cell into the second:
[->+<]
Read left to right: the loop runs while the current cell is not zero; inside it, subtract one from the current cell, move right, add one, move left. When the first cell reaches zero, the loop stops and the second cell holds the old value. Even this tiny job takes careful reading, which hints at the cost of having so few commands.
What gets pushed onto the programmer
When a language removes features, the work does not disappear. It moves. Three things typically move to the programmer:
- Arithmetic. With only increment and decrement, adding two numbers needs a loop, and multiplication needs loops inside loops.
- Naming. There are no variables, only positions. You must remember which cell holds what.
- Structure. There are no functions or named blocks, so reusable pieces exist only as patterns you copy.
The language designer saved effort writing the specification and the interpreter; the programmer pays it back every time.
Small sets are not all alike
Minimalism has more than one flavor. Some languages keep the instruction count tiny but still give the programmer a convenient data structure. The Underload language is built around a stack and a handful of operations that manipulate it, treating programs as strings to be duplicated and combined. Counter machines keep only a few numbered counters with increment, decrement and test-for-zero operations, and they are a classic way to show how little is needed. Tag systems go smaller in a different way, using only a rule for removing symbols from the front of a string and adding some to the end.
Combinator systems reduce things to a tiny set of rewriting functions. Combinator calculus is a good example: a small number of combinators, applied to each other, can express a very wide range of computations, even though the programs bear little resemblance to ordinary code.
Beyond these, there are designs with a single instruction type. Such a language leaves the programmer no choice of operation, so all the variety comes from the operands. They are intellectual curiosities more than practical tools, but they prove a point about how little structure a computing system needs.
The tarpit effect
There is a name for the situation where everything is possible but nothing is easy: a Turing tarpit. The idea is that a language can be so stripped down that, while it can express any computation, the effort of doing so swallows the programmer. Progress is real but slow, and the language offers no help.
This is why tiny languages are often used for demonstration rather than work. They make a clear argument that a particular feature set is enough, and that is their value. They are rarely the best choice for solving a practical problem.
What is lost as the set shrinks
Consider what gradually disappears as commands are removed:
- Speed of writing. Common operations must be built from smaller steps.
- Readability. Programs become long and repetitive, and intent is hidden behind mechanical detail. This is one reason code readability suffers in minimal languages.
- Error checking. With few concepts, there is little for the language to check, so mistakes surface as wrong output rather than a clear message.
- Abstraction. Without a way to name and reuse a piece of logic, patterns are copied by hand, which invites inconsistency.
Not every loss is bad. Fewer commands means fewer rules to learn, and some people find that a small, closed system is satisfying to master. The complete rulebook fits in a few lines, so nothing is hidden.
What a larger set buys
Compare this with a language that has a richer instruction set. The Befunge language, for example, has a larger collection of single-character commands covering arithmetic, comparison, direction changes, stack handling and input and output. That broader instruction set makes ordinary tasks shorter, though the grid layout adds its own complexity. Adding commands is a way of paying in language complexity so that programs can be simpler.
The same tradeoff appears in the hardware world: a processor with many specialized instructions can do certain jobs quickly, while a processor with few can be simple and regular. Neither is universally better.
Choosing a size
There is no correct number of commands. A designer picks a size according to a goal:
- If the goal is a proof or a demonstration, fewer is better, because each removed command strengthens the argument.
- If the goal is a puzzle, a small set creates a pleasant constraint.
- If the goal is everyday programming, a larger set is usually worth its extra learning cost.
Minimalism is best understood as a design experiment. By shrinking the instruction set until something breaks, it shows exactly which features were doing the work, which is knowledge that carries over to the design of any language.