How Humans Learned to Forget the Machine: High-Level Languages
Planted 02026-08-01
What should a programmer no longer have to remember?
How can a person describe an entire program without thinking about the stack that will execute it?
How can someone ask a computer to move a square across a screen without choosing memory addresses, inventing jump labels, or preserving return addresses by hand?
The virtual machine gave software an imaginary computer. The call stack allowed one function to leave its work unfinished, enter another, and return with its world intact.
But the person writing the program still had to manage that world in the machine’s terms:
Push this value.
Push another value.
Add them.
Store the result.
Create a label.
Compare two values.
Jump if the comparison succeeds.
Prepare a call.
Restore the caller.
The machine had acquired arithmetic, memory, instructions, translation, and reusable functions.
The human was still acting as its compiler.
The next transformation would not give the computer a new physical ability. It would change which details a programmer had to keep in mind.
To reach it, people first had to accept a suspicious proposition:
Perhaps a program could be written for a person first and translated for the machine later.
1. The total hidden inside the stack
Suppose a program must calculate the total price of an order.
At the virtual-machine level, one small part might look like this:
push local 0
push argument 0
add
pop local 0
The sequence is exact.
It says where the existing total can be found, where the next price arrives, how both values should be placed on the stack, which operation should combine them, and where the result should go.
A person reading it can eventually recover the intention:
Add the price to the total.
But the intention is not visible all at once. It has been divided into the movements required by a particular abstract machine.
Now write the same relationship another way:
let total = total + price;
The second form is shorter, but length is not the important difference.
It preserves different information.
The VM commands preserve the route taken through the stack.
The Jack statement preserves the relationship among three human-facing ideas:
The old total
The next price
The new total
Someone—or something—must still produce the pushes, the addition, and the pop.
The high-level statement does not eliminate that work.
It moves the work away from the person writing the program.
2. When the programmer became the scarce part
In the early 1950s, electronic computers were extraordinarily expensive. Their time was guarded carefully. It seemed natural to spend human effort protecting machine time.
Programs were prepared instruction by instruction. A mathematical formula had to be decomposed into operations, storage locations, transfers, and branches suited to one particular computer.
The machine might perform those instructions rapidly once they were ready.
Preparing them was another matter.
John Backus encountered that imbalance at IBM while programming the Selective Sequence Electronic Calculator, or SSEC.
One of his early assignments involved calculating the position of the Moon using a long mathematical series. The mathematics could be written compactly on paper. The machine required the calculation to be dismantled into a much larger sequence of explicit operations.
Backus later described machine coding as a miserable business. The computer had been built to save calculation, yet people were spending enormous effort translating calculations into the computer’s anatomy.
This was not merely irritating.
Programming and debugging could consume as much money as operating the machine itself. A faster computer could even deepen the problem: it finished old work sooner and waited for people to prepare new work.
The scarce resource was shifting.
It was no longer enough to ask:
How can the machine run this program faster?
Another question had become unavoidable:
How can people produce a trustworthy program faster?
3. The easier machine that ran too slowly
Backus first tried to reduce the burden with a system called Speedcoding for the IBM 701.
Speedcoding offered operations that were easier to use than the machine’s native instructions. It supplied conveniences for floating-point calculation, input and output, checking, and loading programs.
In effect, it presented a more helpful machine above the physical one.
We have already met this strategy.
Convenient operation
↓
Software performs several native operations
↓
Physical processor
The strategy made programming easier, but convenience consumed machine time. Instructions expressed at the easier level required additional work underneath.
That tradeoff was dangerous.
The computers were costly. Their users cared about the speed of the finished calculation. Many programmers believed that only a person working close to the machine could arrange instructions efficiently enough.
Automatic programming systems seemed to offer an unpleasant choice:
Easy to write, slow to run
or
Difficult to write, fast to run
A high-level language would not matter if using it meant wasting the machine everyone was trying to keep busy.
The next attempt would have to do more than make programming pleasant.
It would have to earn trust.
4. The language that had to defeat its translator
When IBM prepared the 704, some of the services Speedcoding had simulated were moving into hardware. The new machine included floating-point arithmetic and index registers.
Backus proposed something more ambitious.
Instead of giving programmers another list of slightly friendlier machine operations, could a system accept mathematical formulas and produce the detailed program automatically?
In 1954, a small IBM group began developing what became the Mathematical FORmula TRANslating System: FORTRAN.
The name exposed the wager.
Formula
↓
Translation
↓
Machine program
The team included Harlan Herrick, Irving Ziller, Lois Haibt, Peter Sheridan, Roy Nutt, David Sayre, Robert Nelson, Sheldon Best, Richard Goldberg, and others working with Backus. They brought different backgrounds to a problem that did not yet have settled methods.
They also faced a skeptical audience.
A compiler could easily produce some sequence of machine instructions. The difficult question was whether it could produce a good one.
The IBM 704 had few registers and expensive memory operations. A careless translation might repeatedly store a value only to load it again moments later. It might calculate an address several times. It might preserve unnecessary intermediate results. A skilled programmer could see opportunities that a mechanical translator might miss.
If compiled code ran badly, programmers would return to hand coding.
So the FORTRAN team tried to preserve two things that appeared to conflict:
The form convenient for the human
+
The performance demanded by the machine
Lois Haibt helped develop the part that analyzed arithmetic expressions. Other members worked on loops, input and output, register use, and the production of efficient instructions. They tested at night when they could obtain time on the 704. A project expected to take months consumed years.
The delay is part of the story.
Translation was not a dictionary in which one convenient word replaced one machine instruction. A single expression could admit many correct instruction sequences, and some were much more efficient than others.
The compiler had to make decisions the human programmer was agreeing not to make.
5. A formula becomes a program
Consider an expression:
distance = (speed * time) + offset
It resembles ordinary algebra.
Yet even this small line contains structure.
The parentheses say that multiplication must happen before addition.
The current values of speed, time, and offset must be found.
An intermediate product must exist somewhere.
The completed value must be associated with distance.
The names must refer to storage even though the programmer has not written a numerical address beside any of them.
At the VM level, a related translation might be:
push local 1
push local 2
call Math.multiply 2
push local 3
add
pop local 0
Below that are assembly instructions.
Below those are binary instruction fields, ALU control signals, registers, gates, and changing electrical states.
The formula does not replace the lower descriptions.
It adds another description above them.
The important promise is not:
The machine now understands algebra as a mathematician does.
It is:
Write an expression that follows these rules.
The language system will preserve its operational meaning below.
That promise is a high-level language.
6. The language is a set of permitted shapes
A programming language is sometimes described as a way to communicate with a computer.
That description can make the language sound like a conversation.
It is closer to a carefully designed agreement.
Jack, the language used in Nand to Tetris, permits statements such as:
let total = total + price;
if (total > budget) {
let approved = false;
}
while (count < length) {
let count = count + 1;
}
do square.moveRight();
Each form carries a recognizable relationship.
let associates a computed value with a variable.
if makes one block of statements conditional.
while repeats a block while a condition remains true.
do invokes another piece of behavior.
Braces show which statements belong together. Parentheses group expressions and arguments. Semicolons mark the ends of particular statements.
These marks are not decoration.
They constrain the possible meanings so that a translator does not have to understand arbitrary human language.
Jack cannot receive:
Make the square move pleasantly unless it seems too close to the edge.
The request depends on unstated judgments. What counts as pleasant? How close is too close? Which edge? What should happen instead?
Jack asks the person to remove those uncertainties.
The language is higher than VM code, but it is not pure intention.
It is a disciplined place where human intention and mechanical precision can meet.
7. An expression hides a stack
Return to:
let total = total + price;
The statement contains no stack.
That does not mean the stack has vanished.
A compiler can translate the expression by arranging the same protocol the programmer previously wrote:
Find total.
Push its value.
Find price.
Push its value.
Add the top two stack values.
Store the result as total.
For a more complicated expression:
let total = (price * quantity) + tax;
the order matters.
Jack deliberately assigns no general priority among its binary operators. Parentheses make the intended grouping explicit. This makes Jack expressions more cumbersome than expressions in many other languages, but it makes the language simpler to translate.
Here, the parentheses require the compiler to arrange:
price
quantity
multiply
tax
add
The stack’s order becomes the physical history of the expression’s structure.
This is a hidden connection between two layers.
At the source level, the expression appears spatially on one line.
At runtime, its meaning unfolds temporally through a sequence of stack operations.
The compiler preserves the relationship between space and time.
8. A variable loses its address
Assembly language gave memory locations names.
The assembler could let a person write:
@counter
instead of choosing a numerical RAM address.
But the name still belonged closely to storage. It identified a location the program would use.
A high-level variable makes a broader promise.
Consider:
var int count;
The declaration says that count belongs to a particular function invocation and will be treated as an integer.
It does not say:
Place count in RAM location 17.
The compiler may represent it as one offset in the current function’s local segment. Another call to the same function may give its own count a different physical location because it has a different stack frame.
The source contains one name.
Runtime may contain many active instances.
This is the distinction the call stack made possible:
One piece of function text
↓
Many temporary worlds
The language lets the programmer say which world a value belongs to without constructing that world by hand.
9. Control flow without destinations
At the VM level, repetition requires labels and jumps:
label LOOP
push local 0
push argument 0
lt
not
if-goto END
// repeated work
goto LOOP
label END
The destinations are explicit. The programmer names the places execution may go.
In Jack, the same structure can appear as:
while (count < limit) {
let count = count + 1;
}
The loop still requires a beginning, a test, a path into the body, and a path back.
The compiler can invent the labels.
The programmer preserves the reason for the jumps:
Repeat this block while this relationship remains true.
The compiler preserves their geography:
Create a unique beginning.
Create a unique exit.
Branch between them correctly.
Again, the lower mechanism has not disappeared.
Its bookkeeping has acquired a new owner.
10. A function call loses its ritual
The previous machine learned that calling a function requires an elaborate protocol.
A caller must preserve a return address and parts of its current frame. The callee needs access to arguments and local variables. Returning requires placing the result correctly, restoring the caller, and resuming at the saved instruction.
At the VM level, the protocol can be invoked with:
call Order.total 1
At the Jack level, a programmer might write:
let amount = order.total();
The second form preserves relationships that the first only hints at.
order identifies a particular object.
total identifies behavior associated with that kind of object.
The parentheses identify a call and contain any explicit arguments.
The returned value becomes amount.
Far below, the stack still receives an argument the source did not visibly mention: a reference to order itself. The called method uses that reference to find the object’s fields.
The simple-looking dot conceals a protocol:
Use this particular object as the world in which the method runs.
11. An object is organized memory
Jack is an object-based language.
It allows a program to describe a square as a group of related values and behaviors:
class Square {
field int x;
field int y;
field int size;
method void moveRight() {
let x = x + 2;
return;
}
}
To the programmer, one square appears to possess an x, a y, a size, and the ability to move.
Memory does not contain a box labeled square.
It contains words at addresses.
The object exists because several agreements are maintained:
This value points to the beginning of the object.
The first field is stored at this offset.
The second field is stored at another offset.
This method receives the object’s address when called.
These operations interpret the stored words consistently.
An object is not a new material inside the computer.
It is organized memory plus behavior that agrees on the organization.
The high-level language makes that organization visible as a coherent thing.
Instead of repeatedly calculating addresses, the programmer can write:
do square.moveRight();
The machine will eventually manipulate numbers.
The source preserves a world containing a square.
12. The square that completes the climb
The high-level programming project in Nand to Tetris asks the learner to build an interactive application in Jack.
One supplied example allows a person to move and resize a square on the screen using the keyboard.
Consider what happens when the right-arrow key is pressed.
At the highest level, the program may request:
do square.moveRight();
That method changes the square’s position and asks screen-related software to redraw it.
Below the statement is a chain of promises:
Jack method call
↓
VM function and memory commands
↓
Hack assembly instructions
↓
Binary machine instructions
↓
CPU control signals
↓
ALU, registers, and memory
↓
Bits changed in screen memory
↓
Pixels changed on the display
No gate knows that a square moved.
No register knows that a person pressed an arrow.
No stack frame knows that it belongs to a game.
Each layer preserves a smaller relationship.
Together they allow one line written for a person to alter something visible in the world.
The journey from NAND has reached Tetris not because the bottom of the machine became less simple, but because dependable layers accumulated above it.
13. Grace Hopper chooses different words
FORTRAN was shaped for scientists and engineers. Its formulas resembled the mathematical notation its intended users already knew.
But not every person trying to use a computer thought in formulas.
Grace Hopper encountered people working with payroll, inventory, billing, and other data-processing problems. They spoke in names, records, actions, and reports. Dense mathematical symbolism did not necessarily bring their work closer to the machine.
Hopper had already watched programmers damage routines while copying them and make mistakes while adjusting their addresses. Her early compiler-related systems moved some of that repetitive work into the computer itself.
She pushed further toward languages using recognizable words.
FLOW-MATIC statements were organized around verbs and nouns. Its influence later entered COBOL, whose programs could describe business operations using an English-like vocabulary.
The machine had not learned English.
The vocabulary was deliberately limited. The statements followed exact forms. A translator connected those forms to executable operations.
Hopper’s work revealed that high level does not mean one universal distance above hardware.
It can mean closer to the concepts used by a particular group of people.
Scientists may want formulas.
Businesses may want records and transactions.
Game programmers may want objects, coordinates, and drawing operations.
A programming language does not merely hide a machine.
It chooses what kind of thinking to make convenient.
14. Every convenience becomes an obligation
When a language lets the programmer omit a detail, some lower layer must supply it.
If the programmer can write a variable without choosing an address, the compiler must determine where that variable belongs.
If multiplication appears as * but the processor has no multiplication instruction, software must produce the result another way.
If a method can use an object’s fields without receiving each field separately, the calling convention must provide a reference to the object.
If a string can be written between quotation marks, runtime software must create storage for its characters.
If a program can draw a line, some library must decide which screen-memory bits represent the pixels along that line.
Abstraction does not destroy complexity.
It redistributes responsibility.
This is why a language feature is more than punctuation.
It is a promise whose cost must be paid somewhere below.
15. What the programmer is still not allowed to forget
A high-level language removes details, but it does not remove consequences.
A Jack program still runs on a finite machine.
Objects consume memory.
Function calls consume stack space.
Loops consume time.
An array access still reaches some numerical address.
A multiplication still requires physical operations.
The programmer may no longer choose each instruction, but the number and arrangement of requested operations still matter.
The language also introduces its own demands.
The programmer must learn:
Which names are visible here?
Which values have which types?
Which object receives this method call?
Which statements belong inside this loop?
Which operations happen first?
The machine’s details have been replaced by another level of structure.
This is not total freedom from precision.
It is precision applied to a different subject.
16. The human hand moves upward again
Return to the order total:
let total = total + price;
The programmer does not specify the stack.
The programmer does not name an ALU operation.
The programmer does not allocate a register.
The programmer does not choose a RAM address.
The programmer does not encode an instruction.
Those decisions have not become unnecessary.
They have become the work of translators, conventions, and lower layers.
The division of labor now looks like this:
The programmer preserves the intended relationships.
The language preserves a structured way to express them.
The compiler preserves their VM behavior.
The VM translator preserves their machine behavior.
The assembler preserves their encoding.
The CPU preserves their execution.
The gates preserve their logical relationships.
Every level trusts the one below it to recover details it no longer mentions.
Forgetting becomes safe only when another part of the system remembers reliably.
17. What a high-level language really is
A high-level programming language is often defined as a language that is easier for people to read and write than machine code.
That is true, but incomplete.
A high-level language is an agreement about which details may be omitted.
It says:
Describe values with names.
Describe calculations with expressions.
Describe repetition with loops.
Describe alternatives with conditions.
Describe reusable behavior with functions and methods.
Describe related state and behavior with objects.
If your program follows these forms,
the system below will reconstruct the required machinery.
FORTRAN allowed scientists to write formulas without hand-coding every machine operation.
FLOW-MATIC and COBOL brought programming closer to the vocabulary of data processing.
Jack lets a learner describe variables, expressions, objects, and an interactive game without manually arranging every stack operation.
None of these languages makes the computer understand the human world.
They create carefully restricted forms in which parts of that world can be described precisely enough to translate.
That is the transformation.
The programmer has not escaped the machine.
The programmer has gained a dependable place from which the machine can be temporarily forgotten.
But the Jack program is still only text.
To a person, this has visible structure:
let total = (price * quantity) + tax;
There is a destination, an expression, a multiplication grouped inside parentheses, an addition outside them, and a statement that ends at the semicolon.
To the machine receiving the source file, there is initially only a sequence of characters.
Some characters form names.
Some form symbols.
Some marks group one expression inside another.
The same parenthesis may surround a condition or contain a function’s arguments. The same name may refer to a variable, a class, or a subroutine depending on where it appears.
Before the program can descend into VM commands, a translator must recover the structure the programmer sees.
The next question is not yet how the machine can execute a high-level program.
It is more fundamental:
How can a machine learn to read one?