Four Lessons In 40 Years
Mel Conway

 
 

In retrospect, the arrival of the technology described on these pages can be seen as the logical result of thinking for 40 years about one issue: how to make application development simpler.

The consistent thread through my career has been design and development of languages and tools for building useful computer applications, always with the emphasis on simplicity. The trajectory of this career has been influenced by good fortune and personal choice. I describe here how this career trajectory led to the inventions in the patent.

Many of the readers of this paper have no memory of the state of computer technology at the time I started programming in 1956; if you are in this group you might find this essay in techno-archaeology amusing and maybe a bit incredible. 

In the following I distill my experience down to four lessons.

  1. 1.Make the Internal and the External Form of the Application the Same

  2. 2.Static is Good

  3. 3.Application Languages and Algorithm Languages Are Different Animals

  4. 4.Dataflow Conceptual Model + Lessons 1, 2, & 3 = WYSIWYG Development

Only after I completed writing the patent did I look back and realize that what the patent describes is a direct result of all four lessons.

Lesson 1:
Make the Internal and the External Form of the Application the Same

Background

IBM's big scientific computer, the 704, had a minimum memory configuration of 4K 36-bit words. (A 36-bit word held one floating-point number.) I think you could get up to 16K words, but there were 4K machines that had to be taken into account if you were writing software for general use.

Computer science as we know it could not have existed then, because the meager hardware couldn't support the cost of implementing its concepts. What real programmers did in the 50s was struggle to get 20 pounds of computation into a 5 pound bag. To succeed at that was a matter of pride, not as a macho exercise, but because that's how the computing job had to get done. The way you achieved that kind of compression was to come up with a powerful intermediate representation of your problem solution that had enough expressive power so that the memory left over after implementing the intermediate representation (completely in memory, for speed) had space for much more computation than you could write in machine language using the whole memory. (The benchmark of that era was the Bell Laboratories Interpretive System for the IBM 650, which had a memory capacity of 2000 10-digit words. 1000 words held the interpreter, and some pretty big computations lived in the other 1000 words.)

This was the environment in which I learned my craft. As hardware got more powerful, I found that I stayed at my learning edge by hanging out with the smallest machines, below the knee of the power/cost curve, where you couldn't get anything interesting done unless you came up with a powerful computational representation. Thus, when I was a consultant in the 1970's, my most interesting client was Monroe Calculator (now defunct). Around 1972 I learned about the programmable calculator, and I became aware of a dissonance, imperceptible to most but grating to me. For the next 20 years that dissonance bugged me.

The dissonance is between the totally different computational representations that characterize "build" time (i.e., "source" language, what humans read and write) and "run" time (i.e., "machine" language, what computers execute). Computer people take this duality for granted. But in the world of programmable calculators, the only difference between solving a problem for yourself a step at a time or teaching the calculator to solve it autonomously is a feature of the calculator called "learn" mode, in which the calculator remembers the steps you take to solve the problem, and then is able to repeat your steps in "run" mode. The single language of the programmable calculator is its keyboard, whether you are solving a problem for yourself or teaching the calculator to solve it.

Initially in the world of computers there was no dissonance; there was only machine language. (The only gesture toward readability was grouping bits into octal triplets.) Then, beginning in the 1950s, people wanted to program in readable algorithmic languages such as Fortran and Cobol. Because of the memory limitations of the hardware, translating a source program written in Fortran to an object program in the machine language of the IBM 704 required "compilation," a multi-stage process involving storing the two forms of the program as well as intermediate results on magnetic tape. Thus was born the edit-compile-run-debug cycle. Originally this cycle arose from economic necessity; then it became the prevailing paradigm; now it's so ubiquitous that it's invisible.

I reacted to the build/run dissonance in the late 70s by doing some private research to see if I could resolve it with the Pascal source language which, as the world knew, required the edit-compile-run-debug cycle to use. Then, when I saw the Rockwell AIM-65, I found the vehicle for solving this problem below the knee of the power curve, where I most enjoyed working.

The Challenge

I proposed to Rockwell to build an "Instant Pascal" trainer to run on the AIM-65, and the proposal was accepted. The AIM-65 was a single-board computer with an 8-bit Rockwell 6502 (the processor used in the Apple II), 4K bytes (that's right, 4096 bytes) of internal "random-access" memory (RAM), up to 20K bytes of on-board "read-only" memory (ROM), and various byte-at-a-time input-output possibilities. The user interface consisted of a teletype-like keyboard, a single-line 20-position LED alphanumeric display, and a thermal strip printer with 20 characters per line. (The only widely used external storage was an audio cassette recorder.) The intention was that the user would get Instant Pascal in the form of a set of ROMs. You plug these ROMs into the AIM-65 and you have a Pascal trainer.

The AIM-65 had multiple hardware limitations. The major one, of course, was the 4K RAM. But the single-line display wasn't helpful. Clearly, any Pascal editor would have to be a "line" editor, not a "screen" editor. The traditional solution would have been to prepare a program in four steps: (1) the entry step, in which you prepare a source tape; (2) the edit step, in which you modify your source program by doing tape-to-tape copies under control of the keyboard and display; (3) the compile step, in which you read a source tape and write an object tape; and (4) the run step, in which you load the object tape and execute it. Debugging is clearly a problem because you have to have both the source program in view (so you can find and fix your mistakes) and the object program in memory (so the program can run). How can you do that in 4K of memory? And how are you going to do all four (or five, if there is a separate debugger) steps under control of 20K of ROM? I had no intention of building a language trainer that way.

There was an alternative: to store the Pascal source code in the 4K RAM and interpret it directly. Waterloo Pascal1 did that. They had the right idea, but directly interpreting source code made it really slow compared to BASIC, which was the alternative at the time. I was, frankly, relieved to discover how slow Waterloo Pascal was, because I already had the outline of another approach.

The Approach

As with Waterloo Pascal, my solution to eliminating the build-run dissonance was a single internal representation of the program that stayed in RAM. It had to be compact, efficiently interpretable, and readily translatable back to Pascal source so that no source program needed to be stored. The approach I took is today called a "tokenized" representation of the Pascal program. The internal representation of the Pascal program was syntactically correct Pascal: I specified the token language using the formal Pascal syntax. This assured syntactic similarity between external and internal representations of a program. I put one constraint on the user: text had to be entered in "prettyprint" form (no indenting necessary), one line per prettyprint line. Fortunately, this was not a source of complaint, because entering code this way was considered to be good practice; thus this most serious limitation was reframed as a feature. The prettyprint input constraint permitted line-by-line translation into token form (and local syntax checking, with immediate feedback), and there was no need to store more than one line of Pascal text internally.

The parts of the program consisted of (1) a single-line source-code tokenizer/syntax checker ("forward translator" from keyboard to RAM); (2) its reverse, a single-line token-to-source "backward translator" from RAM to printer; and (3) a token interpreter that executed the program. There was a fourth component, a "binder," that scanned the tokenized program after you entered the "run" command, and established (and checked) those syntactic connections that spanned multiple lines. The binder had very little to do, and its execution time was typically imperceptible. Source-code debugging fell out of this design: any line being executed by the interpreter can at the same time be output as Pascal source by the backward translator. During debugging the system could appear to execute at the source level, stop on lines with breakpoints, and evaluate source-level "watch" expressions.

The Lesson

The result of this design was the illusion of executing Pascal source language, but at the speeds of typical BASIC programs. In fact, maintaining the illusion of a single program representation became the principal user-interface design goal. It is this illusion of always dealing with your program at the source-code level that resolves the build/run dissonance and that obviates the need for a separate debugger.

The design was successful and next found itself inside Macintosh Pascal in 1984. But the resolution of the dissonance wasn't complete. Instant Pascal was a trainer, not a construction tool. As compiler writers know, you don't have a real production tool until you can build the tool using itself, and this could not be done.

Lesson 2: Static is Good

Background

My first computer job in 1956 was as a technical support (they called it "applied science") trainee in the Cleveland IBM branch office, supporting an IBM 650 installation at Cleveland Pneumatic Tool, manufacturer of the landing gear for the Boeing 707. The 650 would spend much of its time chugging away at big matrix inversions using the Bell Interpretive System.

Because of its low cost (relative to its predecessors) and IBM's dominant influence, the introduction of the vacuum-tube IBM 650 was probably the major impetus for moving the industrial-computation paradigm from passing punched-card decks (representing data sets such as matrices) through semiautomatic mechanical calculators (such as the IBM 602A) to stored-program, stored-data computation. Before the 650 (if you were not rich enough to have access to a 704), information processing was done with punched-card data sets. This required a different, and very interesting, mindset.

Business information processing was serial file processing; a file was a deck of cards, and each card was typically a record in the file. Doing anything at all required multiple passes of these decks through various punched-card machines. Each file-processing function, such as sorting, collating (merging files or separating merged files), duplicating, or printing, was embodied in a separate type of machine. And each machine (except the simplest, the sorter) was programmed by plugging wires into a plugboard. The then-contemporary equivalents of systems analysts and programmers wired plugboards and scheduled card-processing runs through rooms full of punched-card machines. At the top of the punched-card-machine food chain was the tabulator or "accounting machine," which read a card deck, accumulated sums, printed documents such as management reports and utility bills, and (if a punch machine was connected) occasionally punched out a total card. (In billing applications the total card was the turnaround document that went out with the bill and was returned with the payment. Do you remember “Do not fold, spindle or mutilate?” The word “spindle” was a throwback to a yet earlier storage technology in which a piece of paper was jammed down onto a vertical nail.)

The king of the tabulator hill was the IBM 407, whose plugboard ("control panel") was a couple of square feet in area (the contact holes were perhaps 1/3 inch apart). Almost all of the wires in a typical panel were concerned with routing data (i.e., formatting the printed page), not programming as we would think of it today. A typical wire routed one card column to one position in an adder or on the printed page. The power of the computational paradigm of the 407 (indeed, of all plugboard-programmed punched card machines) lay in what was implied, not in what was expressed by wiring in the panel.

A few years after the 650, IBM introduced the (transistor-based) 1401, which began to move the large body of business punched-card shops from control-panel wiring to stored programming, and from card decks to magnetic tape. IBM recognized that this transition required a major career shift for the workers involved, and it introduced the RPG (report program generator) language to make programming look more like wiring. The original RPG was a remarkable, elegantly simple language that was uniquely successful at capturing the mindset behind the wiring panel. Like wiring, almost all information in RPG specified data formatting. None of the sequential control normally necessary for repetitively processing records was present. I studied RPG to understand why; this is what I learned.

The Underlying Card Cycle

RPG is a language for processing sequential files. In the course of executing a program you read one or two input files and write one output file, with possibly a second or third output file where you write total records and records that have not been processed because of error conditions. File processing is one big loop. Each time through the loop you process a record. In a billing application, for example, the input file contains customer transactions, presorted and grouped by customer number. The customer name and address card occurs once, at the beginning of each group of customer transaction cards. You read in this customer header card (introduced into the deck by a collating machine), then the transaction records, maintaining running totals. There might be "control breaks" (changes in the value of an identifying input field) which signal the need to write a subtotal line if, for example, a sub-account number (also sorted) changes. Finally, when there is a top-level control break (the customer number changes) totals are printed (and punched) out and a new customer is begun. This kind of application is what the 407, backed up by a roomful of smaller machines,  did well.

The 407 has a standard card cycle, as follows. In one "read" cycle one card is transferred from the input hopper to the first read station, the card in the first read station is moved to the second read station, and the card in the second read station is moved to the output hopper. Each of the eighty columns of the two read stations connects to one or two hubs (contacts) in the wiring panel.2 The first read station is used only for comparing control fields, and a special comparator section of the control panel accepts data from the same columns at the two read stations and emits control signals when there is a difference; this is how control breaks are discovered. Card data are processed from the second read station; these hubs can be wired to accumulators (mechanical adders) and printer positions, for example. The control signals from the comparators can be used to initiate special "total" cycles, in which cards are not moved, but accumulators are read out and cleared, and total printing occurs. There can be several levels of total cycles, corresponding to different control/subtotal levels; sequencing from one level of total cycle to the next, and back to the read cycle, is automatic.

The Lesson

RPG is an application language, not a general-purpose programming language; this is the basis of its power. Underlying every RPG program is the 407 card cycle algorithm, hard-coded into the software so that nothing needs to be specified by the programmer. The RPG programming language uses fixed-format cards whose fields recall the sections of the 407 control panel, so that programming is a fill-in-the-blanks operation. For example, if columns 47-53 of a transaction card holds the part number, which is used as a control field for control level 2, that's all you have to say.

Versions of RPG after the first were corrupted by the pressure to turn it into a general-purpose programming language. Fortunately I was able to learn its lessons before its elegance disappeared. Later these lessons enabled me to design extremely simple languages for production programming of specific devices (for example, a primitive typewriter-based Monroe billing machine), based on the following generalizations of the lessons of RPG.

  1. 1.The universe of all applications can be partitioned into classes according to the underlying loop (repetitive processing sequence) that characterizes each application class. For example, file processing operations are characterized by an RPG/407-like loop. Similarly, event-driven GUI (graphical user interface) applications are characterized by an underlying event detection and distribution loop that starts the response to a user-interface event.

  2. 2.For each class of applications there can/should be at least one powerful application development language design that hides this underlying processing sequence. The processing sequence need not be explicit because it is common to all programs in the class.

  3. 3.What remains of an application specification after factoring out the underlying processing sequence is a small, almost static, parameterization of that underlying processing sequence; this is the "application language." (Visual GUI-building tool such as Visual Basic do this for event-driven languages. After the event detection and distribution process has been factored out, what is largely left is a set of static specifications of windows and sub-windows. What sequential processing remains--event-processing routines--has been partitioned by sub-window and event within sub-window, so that these event-processing routines are usually small and independent.)

  4. 4.A measure of success of language design according to this paradigm is how little sequence is left in the application language after the underlying processing sequence has been factored out. Thus, static is good.

Lesson 3: Application Languages and Algorithm Languages
Are Different Animals

The other lesson from the first RPG is the important distinction between languages for algorithms vs. languages for applications. RPG's productivity and approachability as a tool lay in the fact that it was not an algorithm language. It has been my observation that the most productive and easily understood application development languages minimize the demands on the developer to devise sequential strategies. Hence the power of RPG and Visual Basic, (the interesting parts of) which fall in the static application, and not the sequential algorithm, camp.

The job of a static application language is not to eliminate algorithms, but to hide them.

A construction tool based on such a language is an assembler that puts together pieces defined by a quasi-static conceptual model of the class of applications defined by their common underlying sequence. The sequential algorithms are “inside” these pieces, hidden from the application developer.

Lesson 4: Dataflow Conceptual Model + Lessons 1, 2, & 3
= WYSIWYG Development

Having lived through the struggle to write applications for the Macintosh and Windows using algorithm languages like C, I asked, “What are the quasi-static conceptual model and the common underlying sequence of event-driven GUI applications?”

There is the “event loop,” the little bit of code that sorts out user-interface events and sends control to the appropriate part of the application. That is clearly a candidate for the common underlying sequence. But the event loop does not suggest a useful quasi-static conceptual model. Clearly, though, what event-driven GUI applications do is mostly sit around waiting for the next user-interface event. In this respect they are essentially different from file-processing applications, which are continually busy and run as fast as the input-output devices allow.

I have long favored boxes-and-lines flow-network representations of systems.3 There were at the time several boxes-and-lines representations of object-oriented GUI applications, for example the Fabrik project of Dan Ingalls et al.3 This (and others) had problems as candidates for a widely applicable quasi-static application conceptual model. The diagrams did not support multi-level abstraction, in which a portion of a diagram becomes a new box that hides inside it the diagram from which it is defined. This new box must be useful in other, unanticipated, application contexts. This is a requirement for scaling; these models did not scale well.

It became clear that the stuff that flows on the lines from box to box must be meaningful at all levels of abstraction, that is, whether what is in a box is code or a diagram. I concluded that application data (mostly composite data) must be the stuff that flows, not object-to-object messages as in several other models.

Then I addressed how user interface events are to be handled. If events at the user interface generate “event flows” away from the user interface, figuring out what path these event flows follow causes complexity to explode. I concluded that flows must be unidirectional toward the user interface and hence there could not be event flows as distinct from data flows. This led to a hybrid flow/message processing model.

Then, guided unconsciously by Lesson 1, I asked whether the internal structure of the executing application can be the same as the structure of its boxes-and-lines network, so I designed that.

That led to a design problem: how to schedule flows so that, after an event, the network is guaranteed to reach a new equilibrium. The algorithm for scheduling these flows became the common underlying sequence for this application conceptual model. Consequently the network flow diagram is a topological, and not a sequential, specification.

Since both the network-drawing tool and the application under construction are event-driven GUI applications, they spend most of their time just sitting around waiting for the next event. Thus they can both be “executing” (i.e., waiting around) simultaneously. And since changing a diagram does not lead to a nonlocal restructuring of the program (because the network diagram and the application are isomorphic), application behavior tracks flow network diagram editing transparently, and what you have is a WYSIWYG application builder.


1. University of Waterloo, Waterloo, Ontario, Canada.

2. The cards are read broadside so the zero row is read first by 80 contact brushes, then the 1 row, etc. Digit information is expressed by the timing of a pulse that occurs when a hole falls under a brush, allowing current to flow in one of the 80 current paths.

3. Sequential file processing procedures, including punched-card and magnetic-tape processing applications, were conventionally documented this way.

4. D. Ingalls et al, “Fabrik: A Visual Programming Environment,” Proceedings of OOPSLA (Conference on Object-Oriented Programming Systems, Languages, and Applications), September 1988

© Copyright 1997, 2006, 2010 Melvin E. Conway