As a discipline, Computer Science is operating with an outdated notion of what computation is. That is, our underlying field-specific mythology - the common set of presumptions on which we base our work, and into which we initiate our community's newest members - lags far behind the state of our computational practice.
I take our introductory course to be representative of what we believe our field's foundations to consist in. Although there is obviously much advanced knowledge that goes beyond what we can possibly teach to the average college freshman, we do intend our introductory courses to convey the mindset and assumptions that shape us as a discipline. This is so no matter which of the many extant variants of "CS1" we teach. (In general, CS1 is a programming course, because programming is both a tool and a basic language for our field; but this is not necessarily so.)
In almost every introductory programming course, we teach computation as sequential problem-solving. A programmer's problem is to describe the series of steps to be taken in order to arrive at some desired result. A simple example might be the construction of a peanut-butter and jelly sandwich: First assemble the ingredients, then open the peanut butter jar and begin to spread....until eventually a sandwich is complete. Appropriate questions can be asked: "How long did it take?" "How many knives were dirtied?" "Was the optimal peanut-butter and jelly sandwich produced?" A more realistic example might be a word-processing program: First, produce the text. Next, run it through a spell-checker to construct an improved version of the text. Then, use a text formatter such as LaTeX to produce a printable/viewable image. Finally, send it to a previewer or to a printer, producing the appropriate camera-ready copy.
This approach to computation is characterized by the functional nature of its constituent parts. A great innovation over simple sequencing is the notion of procedural abstraction, which allows us to treat a sequence of steps as though it were itself a primitive step. For example, we may have a single routine for spreading a substance, usable for both the peanut butter and the jelly. This allows programming to be redescribed in terms of recursive decomposition: break down the problem into a sequence of steps, then break down each step into smaller steps, and so on, until the individual steps are directly executable. Nonetheless, in this model, there is a single thread of control and the programmer owns it.
But this is not in fact how computation is practiced. Take, for example, a modern incarnation of the rather old-fashioned word-processing program described above. As I write this essay, my modern word processor automatically adjusts the formatting of the entire page, displaying at all times camera-ready output. When I misspell a word - type "hte", for example - the "h" and the "t" automatically reverse themselves. One could imagine other processes critiquing my prose as I compose it or finding appropriate references in the on-line literature. That is, there are many things happening in the process of my composition, and they happen in parallel. Further, the word processor is judged not merely by the result it produces, but also by the way in which it interacts. (Does it correct my spelling when I haven't misspelled a word?)
Rather than constructing a peanut butter and jelly sandwich, designing a program is like organizing a luncheonette.
Programming, in this model, is the problem of constituting a community of interacting entities. Once you know what problem you're solving - e.g., what services you will be providing - you need to know who the entities are that together constitute your program. You must decide how these entities will interact, ideally in controlled and well-specified ways. Finally, you must design the behaviors that go inside each entity, recognizing the power of recursive decomposition: an entity may itself be a community of simpler entities.
A program of this type is judged by the set of services or behaviors it provides. (In modern parlance, these systems are sometimes called servers or agents.) This description characterizes robots and software agents; it is descriptive of operating systems and network services; it fits video games and spreadsheets and modern word processing programs,as well as the controllers for nuclear power plants and automotive cruise controls. In short, most software written today is of this sort. Yet we do not teach this model to our students.
Even in this model, peanut butter and jelly sequential problem-solving computation has its place. It is one aspect of how entities are constructed. However, peanut butter and jelly leaves out entirely the "how entities are interconnected" part of the problem. In modern computational systems, much of the interesting behavior is generated not by individual components per se, but by the interactions among these components. Interface design - in every sense - replaces functional encapsulation as a means of separating component structure ("what goes inside") from component integration ("what goes between").
I came to this work from artificial intelligence, though I have experience in a variety of other computational fields as well. It is clear to me that the software that I write and interact with no longer adheres tightly to the sequentialist paradigm. Others, too, have noticed the interconnectedness of software systems. (See, e.g., Wegner's recent article in the Communications of the ACM or Smith's On the Origins of Objects, MIT Press, 1996, or any of the recent work in component architectures.) New approaches to computation are needed at many levels, from theoretical foundations to design methodologies. This is less a shift in the systems that we build and more an upgrading of our understanding of these systems.
For decades, our ideas of what computation is were based on the enabling notion that computation is a sequence of steps executed by a single sequential CPU. This enabling metaphor allowed us as a discipline to make tremendous progress. Even in the earliest days, this metaphor relied on such ideals as the digital abstraction; nonetheless, the ways in which Turing's machine was an idealization could in many cases be overlooked. Indeed, we worked hard to maintain our sequentialist illusions. Even models of parallel problem-solving often strive to approximate the behavior of sequential execution, only faster.
Increasingly, though, the ideal of sequential computation on a dedicated CPU is not merely difficult to maintain but downright detrimental. Imagine running a luncheonette if from the beginning you were told that a single person would have to manage all aspects of the business in a rigid single-tasking mode. (No timesharing, for example, and no taking into account how long the eggs need to cook while you're listening to someone's order.) As another example, consider the relatively new Java programming language: every Java program with a graphical user interface (GUI) is inherently concurrent. How can we use Java as an introductory programming language and yet hide this fact from our students?
My own work addresses the question of what computation looks like if we remove these assumptions and live from the very beginning in a potentially concurrent, distributed, interactive, embedded environment. In particular, what replaces the stories that we now tell our freshmen? After all, if we know how to introduce students to this mindset, we will know a great deal about the mindset itself as well as what its most crucial aspects are.
As a single concrete example, the traditional first program - the atomic unit of computation, old-style, input-processing-output-halt - is print "hello, world"; - no input, little processing, just output and halt. In the reconceptualized introductory course, this nugget is replaced by a similarly simple but decidedly different program: while (true) { echo(); } [Here, echo(); is the program that reads a line of input and writes it back to the user, a common utility.] This program is the atomic unit of the new vocabulary: an infinite loop that senses and reacts, handles service requests in turn, or behaves, in the simplest sense.
My current project is to write the textbook for a new introductory computer science curriculum based on this reconceptualization of computation. For the reasons that I have given above, and others I have argued elsewhere, I believe that a radical redesign of the introductory course is crucial for our field. It affects not merely our pedagogy, nor just our next generation, but also our own self-image, including the tools and metaphors that we use to conceptualize our work.
Over the past few years, I have developed such an introductory programming course, and for the past two years, I have been successfully teaching it: it twice in the regular curriculum and twice as a one-week intensive minicourse in the summer session. The majority of students have no prior programming experience; most are college freshmen.
My curriculum begins, like most other introductory programming courses, with the basic syntax and semantics of programming constructs. At the same time, and from their very first programming assignment, students are writing concurrent, interactive, embedded programs. For example, an early program has students designing a control agent that sits on one end of a virtual teeter-totter, or see-saw. We provide most of the environment with which their code interacts; they write only simple expressions. Because their programs' behavior depend not just on the code they write, but also on the other agents with whom their code interacts - for example, the code at the other end of the see-saw or even the see-saw itself - students get early lessons in the relationship of behavior of entities to behavior of aggregates.
After the first month, we turn from programming basics to the question of entity architecture. In this segment, we look at the similarities and differences between an explicit control loop and event-driven programming. Students also learn about modern GUI design. Safety and liveness - and the parameters within which such issues may arise - are discussed. We then proceed to client-server architectures and the importance of interface specification.
In the final month of the class, we examine conventional patterns by which complex concurrent and distributed systems are constructed. We build systems whose constituent entities are themselves communities. The emphasis is on designing and understanding a variety of interactive communities. Last year, the students' final project was an interactive, networked, client-server chat/drawing program. (Remember, these are first-semester freshman without prior experience!)
There are many advantages to this new approach. The curriculum includes topics not often taught to our introductory students: networks and user interfaces, client-server and event-driven programming. At the same time, students leave this new course with a basic facility for programming and for problem decomposition, the most crucial skills taught in most existing CS1 courses. Importantly, this new CS1 course thus requires little revision of the later computational course sequence. It does, however, enable a wide range of within-course changes that are desireable for but currently beyond the reach of most undergraduate curriculum. (For example, parallel search and sort algorithms can now be easily integrated into an algorithms course; parallel complexity into theory. Operating systems no longer teaches both the notion of concurrency and its implementation. And the user, whose role was never entirely clear in the sequentialist paradigm, now fits clearly and neatly into the community that is the program's own environment.)
This approach to computation also has the potential to build bridges with neighboring and less-obviously related disciplines. For example, the emphasis on harnessing and controlling interactions shares much with mainstream engineering. The community model of computation is similar to foundational concepts in organizational science and other social sciences.. Indeed, the idea that computation's effect is defined in terms of its (environmental) interactions is almost deconstructionist! All of these fields currently understand the computational metaphor as rigidly lock-step sequential problem solving. Reconceptualized, the truth of computational practice is much closer to complex systems engineering.
The current version of my course is on the web at http://www-cs101.ai.mit.edu/
My current project is to prepare a textbook and other supporting materials (based on currently existing course noes and materials) for wide dissemination. Initial interest has been positive, both from educational institutions and publishers. In addition to the textbook, I would plan to teach one or more mini-courses to computer science educators interested in adopting such an approach. Effecting such a change in the way that introductory computation is taught is not a small project; however, enough work has already been done, and the need is clear enough, that I believe that such a project would have enormous potential impact.
In addition, I would like to extend this work beyond the computer science community. I believe that computation is in general not well-understood, and the kind of computation that is at the heart of my project is an extreme case-in-point. The style of my existing course notes - and the likely style of the textbook I would write - is such that I believe that there would be a possibility of transforming some of the more evocative, less technical aspects into a popular book on computation. (For example, the peanut butter and jelly versus luncheonette examples, in much greater detail, and the word processing program paradigm shift, are accessible even to non-specialists. The syntactic details of programming constructs, crucial for the introductory student, would of course be omitted from a popular book.)
Such a book would be an important piece of public relations for the entire computational community as well as a contribution to public education and scientific literacy. It is important that the general public understand something about the nature of the computational infrastructure on which they are increasingly dependent; but existing depictions of computation are, like computer science's own self-images, overly sequentialist. (Alternately, they are so far removed from a technical understanding of computation that they do not give insight into the programmer's world.)