The first compiler was written by Grace Hopper in 1952 while the Lisp interpreter was written in 1958 by John McCarthy's student Steve Russell. Writing a compiler seems like a much harder problem than an interpreter. If that is so, why was the first compiler written six-years before the first interpreter?
Writing a compiler seems like a much harder problem than an interpreter.
That might be true today, but I would argue that it was not the case some 60 years ago. A few reasons why:
- With an interpreter, you have to keep both it and the program in memory. In an age where 1kb of memory was a massive luxury, keeping the running memory footprint low was key. And interpreting requires a bit more memory than running a compiled program.
- Modern CPUs are extremely complex with huge catalogs of instructions. So writing a good compiler is truly a challenge. Old CPUs were much simpler, so even compilation was simpler.
- Modern languages are much more complex than old languages, so even compilers are much more complex. Old languages would thus have simpler compilers.
The fundamental point is that the computing hardware environment of the 1950s made it such that only a compiler was feasible given the batch-oriented processing of computers back then.
At the time the better user interfaces were primarily limited to punch cards and teletype printers. In 1961 the SAGE system became the first Cathode-Ray Tube (CRT) display on a computer. So the interactive nature of an interpreter was not preferable or natural until much later.
Numerous computers in the 1950s used front panel switches to load instructions, and the output was simply rows of lamps/LEDs, and hobbyists even used front-panel switches & LEDs into the 1970s. Maybe you're familiar with the infamous Altair 8800.
Other hardware limitations also made interpreters unfeasible. There was the extreme limited availability of primary memory (e.g. RAM) in computers in the 1950s. Prior to the semiconductor integrated circuit (which didn't come until 1958) memory was limited to magnetic core memory or delay line memory which was measured in bits or words, no prefix. Combined with the slowness of secondary storage memory (e.g. disk or tape), it would be considered wasteful, if not unfeasible to have much of the memory used for the interpreter, even before the program being interpreted was loaded.
Memory limitations were still a major factor when the team lead by John Backus at IBM created the FORTRAN compiler in 1954-57. This innovative compiler was successful only because it was an optimizing compiler.
Most of the computers in the 1950s barely had any Operating System, let alone modern features such as dynamic linking and virtual memory management, so the idea of an interpreter was too radical and impractical at that time.
The languages of the 1950s were primitive. They included only a small handful of operations, often influenced either by the underlying hardware's instructions or the problem definition of their targeted use.
At that time, computers were rarely general purpose computers in the sense that we think of computers today. That they were reprogrammable without having to be rebuilt was considered a revolutionary concept -- previously people had been using electromechanical machines (typically calculators) to compute or calculate answers (the majority of applications in the 1950s were numeric in nature).
From a Computer Science point of view, compilers and interpreters are both translators, and roughly equal in complexity to implement.
The first programming languages were quite simple (no recursion for example) and close to machine architecture which was itself simple. The translation was then a straightforward process.
A compiler was simpler as a program than an interpreter that would have to keep together both the data for program execution and the tables to interpret the source code. And the interpreter would take more space, for itself, for program source code and for symbolic tables.
Memory could be so scarce (for both cost and architectural reasons) that compilers could be stand-alone programs that overwrote the operating system (I did use one of these). The OS had to be reloaded after compiling in order to run the compiled program. ... which does make it clear that running an interpreter for real work was simply not an option.
To be true, the simplicity required of compilers was such that compilers were not very good (code optimization was still in infancy, when considered at all). Hand written machine code had, at least until the late sixties in some places, the reputation of being significantly more efficient than compiler generated code. There was even a concept of code expansion ratio, that compared the size of compiled code to the work of a very good programmer. It was usually greater than 1 for most (all?) compilers, which meant slower programs, and, much more importantly, larger programs requiring more memory. This was still an issue in the sixties.
The interest of compiler was in programming ease, especially for users who were not computing specialists, such as scientists in various fields. This interest was not code performance. But still, programmer time was then considered a cheap resource. The cost was in computer time, until 1975-1980, when the cost switched from hardware to software. Which means that even compiler were not taken seriously by some professionals.
The very high cost of computer time was yet another reason for disqualifying interpreters, to the point that the very idea would have been laughable for most people.
The case of Lisp is very special, because it was an extremely simple language which made it feasable (and the computer had become a bit bigger in 58). More importantly, the Lisp interpreter was a proof of concept regarding the self definability of Lisp (meta-circularity), independently of any issue of usability.
Lisp success is due largely to the fact that this self definibility made it an excellent testbed for studying programming structures and design new languages (and also for its convenience for symbolic computation).
I disagree with the premise of the question.
Adm. Hopper's first compiler (the A-0) was more like a linker or a macro language. She stored subroutines on a tape (each one assigned a number) and her programs would be written as a list of subroutines and arguments. The compiler would copy the requested subroutines from tape and re-order them into a complete program.
She used the word "compile" in the same sense that one compiles an anthology of poems: collecting various items together into one volume.
That first compiler did not have a lexer or parser, as far as I can tell, which makes it a distant ancestor of a modern compiler. She later created another compiler (the B-0, a.k.a. FLOW-MATIC) with the goal of a more English-like syntax, but it wasn't completed until 1958 or 1959 -- around the same time as the Lisp interpreter.
Therefore, I think the question itself is a bit mistaken. It seems that compilers and interpreters co-evolved almost exactly at the same time, no doubt due to the sharing of ideas that would have had many scientists thinking along the same lines in those days.
A better answer with citations here: https://stackoverflow.com/a/7719098/122763.
The other part of the equation is that compilers were a step abstraction above an assembler. First we had hard-coded machine code. "We" were the assembler. Every jump and offset, etc. was hand-calculated into hex (or octal) and then punched into paper tape or cards. So when assemblers came on the scene, it was a huge time-saver. The next step was macro assemblers. That gave the capability to write a macro that would expand into a set of machine instructions. So Fortran and Cobol were a huge step forward. The lack of resources (storage, memory, and cpu cycles) meant that general purpose interpreters had to wait for technology to grow. Most early interpreters were byte-code engines (like Java or CLR of today, but with much less capability). UCSD Pascal was a very popular (and fast) language. MS Basic was a byte-code engine under the hood. So the "compile" was really to generate a byte-code stream that was actually executed by the runtime.
In terms of instruction overhead, it totally depended on what processor was being run. The industry went through a big RISC vs CISC turmoil for a while. I personally wrote assembler for IBM, Data General, Motorola, Intel (when they showed up), TI, and several others. There was a fairly significant difference in instruction sets, registers, etc. that would influence what was required to "interpret" a p-code.
As a time reference, I started programming in the phone company around 1972.
If you are not holding everything in memory, compiled code is much more quick. Don't forget, that in these times functions were joined to the compiled code. If you are not compiling, you do not know what functions you'll need. So, you are calling functions from... Oh, not from disk yet, we are in early 50-ties, but from cards! In runtime!
Of course, it is possible to find a workarounds, but they were not found yet, for languages were very simple and not so far from machine code. And compiling was easy and enough.
Before the first compiler was written, people wrote assembler code which was a huge progress compared to plain binary code. At the time, there was a strong argument that code compiled by a compiler would be less efficient than assembler code - at that time the relation of (cost of computer) to (cost of programmer) was much, much different than today. So there was strong resistance against compilers from that point of view.
But compilers are an awful lot more efficient than interpreters. If you had suggested writing an interpreter at that point in time, people would have thought you are crazy. Can you imagine buying a million dollar computer and then wasting 90% of its power to interpret code?
Before a looping program can be interpreted, it must be stored in a medium which can be read repeatedly. In most cases, the only suitable medium would be RAM. Since code will typically be entered on punched cards which--for human readable languages--are likely to be largely empty, some sort of processing must be performed upon the code before it is stored in RAM. In many cases, processing the punched-card text into a form which is suitable for direct execution by the processor is not really any more difficult than processing it into a form which could be efficiently handled via an interpreter.
Note that the goal of the early compilers was not to produce an assembly-language or object-code file on disk, but rather to end up code in RAM that was ready to execute. This is actually surprisingly easy when there's no operating system to get in the way. A compiler can generate code starting at one end of memory and allocate variables and branch targets starting at the other. If a statement is marked with label "1234", the compiler will store in variable called "1234" an instruction to jump to the current code generation address, creating that variable if it doesn't exist. A statement "goto 1234" will create a variable "1234" if it doesn't exist, and then jump to that variable [which will hopefully have a jump to the proper location stored in it before that statement executes]. Note that the compiler doesn't have to do anything special to allow code to
goto a label which hasn't been defined yet, since it knows when the
goto compiles where it's going to jump--to a variable. That may not be the most efficient way to generate code, but it's adequate for the sizes of programs that computers were expected to handle.
Because interpreters need compilers in order to work.
The above statement isn't really true. Strictly speaking, you can make an interpreter without ever using or otherwise interacting with a compiler. But the results of doing this wouldn't wouldn't look very much like what I think you mean by those terms.
In the strict sense, compilers and interpreters do entirely different things. A compiler reads text from some source, and transforms it into some other format: assembly language, machine code, another high-level language, a data structure, or whatever else. An interpreter, meanwhile, takes in a data structure of some kind, and performs instructions based on it.
What we tend to think of as a "compiler" nowadays is actually a compiler that has been paired with a code generator: a program that takes in data from some source, and outputs code in some format based on what it sees. That's a fairly intuitive use for compilers, and it was one of the first things compilers were created for. But if you look at it another way, this seems very similar to what an interpreter does. It always outputs code instead of performing more general operations, but the principle is the same.
If we look at it from the other side, an interpreter needs to get its data from somewhere. This is just data, so you can build it up in the same ways you'd build up any other kind of data. Since we're talking about interpretation, it seems natural that you could build your data based on instructions in a text file. But to do that, you'd need something to read in the text and create your data structure, and that's a compiler. It's hooked up to the interpreter instead of to a code generator, but it's a compiler all the same.
That's why compilers were written first. The idea of interpreting data structures wasn't new even when compilers were conceived, but compilers were the "missing link" that let programmers build those structures out of text.