Lecture Notes for Com Sci 221, Programming Languages
Last modified: Fri Jan 27 15:11:19 1995
Very roughly, progress in programming languages is measured by the use of higher and higher "levels" of languages. That is, languages that allow the structures of programs to correspond more closely to the structures of the problems that they solve, and be less constrained by the structures of the machines that execute them. In the bad old days, there was an overemphasis on run-time efficiency. That is, we assumed that we knew exactly what a program should do, and a number of ways to make it do so, and the action was in making it do so as quickly as possible, using as little memory as possible. This assumption favored programming languages that followed the structure of the underlying machines very closely. Such programming languages were also relatively easy to design and implement. By now, it is clear that the cost of using computer software is determined overwhelmingly by the cost of testing, debugging, modifying, and otherwise maintaining programs, rather than by the cost of running them, or even of writing them in the first place.
Higher-level programming languages, if well designed and implemented, make the truly costly part of the software business more efficient. There is often some cost in run-time efficiency, but overall it is negligible compared to the savings in human labor. Mature implementations of high-level languages even beat human-coded machine language for fun-time efficiency in many cases. It is easy to be intimidated by a person, posing as one of the cognoscienti of computing, who denigrates careful structuring and claims that "practical" code in the "real-world" is arcane and full of intricate and obtuse tricks to gain efficiency. In the overwhelming majority of cases, the only "practical" aspect of overly clever code is that it allows the programmer to earn more salary accomplishing less. Good code is code that explains itself, not code that requires a specialized expertise to understand.
It may seem that the proper future of computing will make all programming languages obsolete, by allowing people to merely describe what they want from a program, and applying some sort of "automatic programming" to do the hard work. There is good reason for skepticism. Many years ago, the problem of "automatic programming" was declared to be solved. The solution was the assembly language. In my opinion, while the amount of detail worked out automatically by a programming language implementation will become more and more impressive, and the pointlessly difficult or tedious parts of programming will be removed, whatever language is used for communicating with computing machines will be recognized as different than the language in which we ask for an ice cream cone. Conceivably, it will not be called a "programming language," and conceivably it will be expressed in a natural language form, such as English. But, the sort of English used to communicate specialized problems precisely will necessarily be a somewhat special and precise sort of English. It is the complexity of the problems that we wish to solve with computers, and the precision that we demand in the solutions, that makes programming difficult. As more and more sophisticated problems are solved, and put in the can, we will expect programmers to accomplish more, rather than expecting programming to be easier.
Perhaps we need a concrete example of the meaning of "structure" in a program, to ground all of the abstract discussion. Recall from the introductory lecture that the structure of a program resides in the relations of the program to other things. Now, consider the problem of summing lines of integers, solved in schematic pseudoPascal in two different ways:
while there is another line of input do sum := 0; while there is more input on this line do read(next); sum := sum + next end while; write(sum) end while
Summing lines of integers, method A
read in all input to a 2-dimensional array; sum each row to a 1-dimensional array; write the sums
Summing lines of integers, method B
Now, shift your attention back to the progress of programming languages from "low-level" languages that tie the structure of a program to the structure of a machine, toward "high-level" languages that allow the structure of a program to mimic the structure of the problem that it solves. Starting with the machine language of a von Neumann/RAM ideal machine, "higher-level" programming languages have so far tended to progress along two different dimensions:
In order for a newly designed programming language to be used, it must be somehow implemented in terms of previously implemented languages. Every chain of language implementations must lead eventually to a machine language, which is implemented by the hardware (wiring and physical components) in a machine, rather than by software. There are two basic types of programming language implementation in common use today:
Generally, a compiler invests a certain effort in the initial translation of a program, in order to be able to execute the program repeatedly. Execution requires only the object code: no further reference to the source is needed. When a program is to be executed repeatedly, and efficiency is important, a compiler is usually used for the implementation. Interpreters are sometimes faster for a single execution of a program (although even a large number of iterations of a loop body may easily throw the advantage to a compiler even for a single execution). Interpreters are usually easier to write than compilers, and it is usally easier to provide debuggers and other useful programming tools with interpreters. All of these general rules have exceptions, though. So far in the history of computing, compilers are the dominant method for producing commercial software, but interpreters are used for most experimental implementations of radically new programming languages.
When a program is compiled and executed, the total work and resources are often analyzed into
Here comes another ridiculously long list. These lists are not things to try to memorize. They have no pretensions at being complete or definitive. But, it is important to think briefly and incisively about the items on the lists, to get an idea of the broad scope of the issues that affect programming languages. So, here is a list of a few of the qualities that affect the value of particular programming languages and their implementations:
Such a superficially holistic approach is often a valid way to choose a particular language to use right now for a short-term task. But, it is no way whatsoever to try to understand the underlying structural concepts that can lead to fundamental progress in programming languages. For example, when you use Prolog you may observe that many aspects of the notation in which programs are written are incredibly awkward. That is a true, but uninteresting, observation about Prolog. The thing to learn from Prolog is the potential power of its radically different basic computational mechanism. You will understand how to think about programming languages when you are able to critique separately and independently those parts of design and implementation that are in fact separate and independent. Research progress has tended to remove dependencies that were long assumed. For example, when Algol was first promulgated, recursive procedures were seen as a cute programming tool for quick and inefficient programming, but inherently slow to execute, and never suitable when speed is important. Since then, recursion has in fact been made quite fast, and in many contexts may be used quite casually.
In this course, we will focus on the suitability of programming languages to compute particular functions from input to output with modest efficiency. We will occasionally consider interactive tasks (where a particular interleaving of input and output is required), but not real-time problems (where the precise timing of inputs and outputs is crucial). We will take a particular interest in the structural issues that make programs easy to write, understand, and modify. There is nothing fundamentally special about these characteristics, but they are where the most interesting progress has been made in programming language design so far.