Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

79KB

The Psychology of Computer Programming — Chapter-12: Some Principles for Programming Language Design

  • Source: /library/The Psychology of Computer Programming/source-file.pdf
  • PDF pages: 277–307
  • Pages without text: none
  • Note: Includes the Silver Anniversary comments that immediately follow the original chapter. Text comes from the scan's OCR layer and may contain recognition errors.

SOME PRINCIPLES FOR V7) PROGRAMMING == LANGUAGE DESIGN | aving said that the elucidation of programming language de- sign principles is an empirical matter, we shall now proceed to give a largely speculative survey of some ideas that have been put forth as general principles of good design. The pres- entation must be largely speculative simply because most of the em- pirical data do not exist. Yet such data are not necessarily difficult to obtain—most of the studies which we can cite were done by students working with limited time and low budgets. The real problem is that psychological factors are not recognized as part of the programming language design problem—or, if they are recognized, they are thought to be solvable by introspection on the part of the language designer and his pals. 217

218 Programming Tools UNIFORMITY In some cases, we can draw experimental data from the mainstream of psychology, at least for suggestions as to areas of potential program- ming difficulty. One such case is the principle of uniformity. The dif- ficulty of remembering a set of items has been shown in numerous experiments to be related to the “information content’ of the set. The “information content,’ however, is difficult to measure, for it depends not only upon what is in the set, but on what is not in it—what is in the rememberer’s mind. If, for instance, we ask a subject to memorize the set 000 001 010 011 100 101 110 111 he will have little difficulty if he knows the binary number system. But suppose the subject knows the binary number system and is asked to memorize the set: 000 001 010 100 101 111 Now, although there are only six cases to remember, the job is more difficult. Why should that be? Because the complexity of remembering —for the person who knows the ‘‘system’’—is not related to the number of cases, but to the number of exceptions from the system. Once one knows the binary number system, it is just as easy to ‘memorize’ the first 1024 numbers as it is to memorize the first eight. In our first example, the subject only had to remember ‘“‘the first eight binary numbers,” but in the second example, he might have remembered in the form, ‘the first eight binary numbers—excluding 011 and 110.’’ We do not have to be expert psychologists to see that the second case is at least no easier than the first, and should be somewhat harder. Now, the preceding argument would be invalid if the subject was naive with respect to the binary number system, for then the two lists might appear more or less ‘random’ to him. In that case, six items should be somewhat less difficult to memorize than eight—exactly the opposite result obtained by the more sophisticated subject. In programming languages, precisely the same situation prevails. In the last chapter, we mentioned the case of FORTRAN subscripts. If programmers were perfectly naive on the subject of arithmetic expres- sions, we could argue that the FORTRAN subscript rules were easier to remember than, say, the PL/I or APL subscript rules, for the set of possible subscripts is obviously smaller in FORTRAN. But programmers are not perfectly naive, and they can learn most possible PL/I or APL subscripts by remembering only one simple rule: ‘‘any scalar expression can be a subscript.” In FORTRAN, however, they must learn the following set of seven forms:

Some Principles for Programming Language Design 219 a Vv vt+a v—a av av + b a*v — b where v represents an unsigned, nonsubscripted, integer variable, and a and b represent unsigned integer numbers which must be greater than zero. In order to test the difficulty of remembering these rules, a survey was taken of 117 experienced FORTRAN programmers. First we asked them to say whether the expression 21 — K was a valid FORTRAN subscript, an invalid FORTRAN subscript, or they didn’t know for sure. 31 said it was valid, 9 said it was not valid, and 77 said they were not sure. (As a control, we asked 53 experienced PL/I programmers the same question about PL/I, and all but 4 said it was a valid subscript; these 4 were former FORTRAN programmers.) Following this test, we asked the 9 correct FORTRAN programmers to write down the exact rules for permissible FORTRAN subscripts. None of the 9 were able to give the rules exactly. Of the 49 correct PL/I programmers, all were able to give the answer roughly equivalent to “anything.” Although this is not an exact rule—it encompasses, for ex- ample, array expressions, which are not valid—it is a rule which at least does not prevent the programmer from trying something that is valid, such as K-21 in FORTRAN. If the PL/I programmer does, indeed, happen to write an array expression as a subscript, he will be caught in the act by the compiler; but a FORTRAN compiler is not likely to tell the K-21 programmer that he is not using the full power of the language. What we mean, then, by uniformity should be reasonably clear from this example, although no precise statement of the principle can be given, since it is a psychological principle rather than a mathematical one. Roughly speaking, the principle of uniformity in programming language design might be stated thusly: “The same things should be done in the same way wherever they occur.” To the extent that the language contains deviations from this rule, it violates the principle of uniformity and will thus be more difficult to learn, more difficult to use without error, and more difficult to use for producing ‘‘new” techniques. We explored the question further by comparing two languages, one of which was a “subset” ot the other, used on the same problem. The two languages were the OS/360 and the DOS/360 versions of PL/I. The DOS version is not a perfect subset of the OS version, but to a large

220 Programming Tools extent it can be characterized as such—as the OS version with certain restrictions and omissions (which are a special case of restrictions). With 14 relatively new PL/I programmers working in each language on the same problem, we found that the OS group had detectably less difficulty in getting working versions and that the code was shorter. In analyzing the different programs produced, we found that the DOS programmers often failed to use a feature that was, in fact, in their subset. When asked about this, they invariably replied, “I wasn’t sure | could write that.” (See Figure 12-1.) What seems to happen, then, is that the more “covert categories” —things that you cannot do or say—there are, the more one expects other such covert categories in the language. Even if the restrictions are in another part of the language, they may affect the actual usage of a part without such restrictions. Another way of putting the rule of uniformity, then might be this: “If a programmer asks, ‘Can | write... ?’ the answer should be ‘yes.’’”’ Just like the child who is told “no” too often, the programmer working in a nonuniform language will tend to be discouraged from trying new things. Arother important aspect of uniformity is that the same _ syntactic construction should not mean different things in different contexts. For example, we are told in PL/I that “redundant parentheses are never required, but may always be used if desired.” This is a good rule, but one which, unfortunately, has a few fairly critical exceptions. For in- stance, the two statements CALL X(A); CALL X((A)); Figure 12-1 Comparison of a programming language with its subset.

Some Principles for Programming Language Design 221 do not usually mean the same thing at all, for the second always causes a dummy argument to be passed to X (roughly, a call-by-value instead of a possible call-by-reference). On the other side of this coin, the two statements GET LIST(A(I) DO | = 1 TO N); GET LIST((A(I) DO | = 1 TO N)); do not mean the same thing because the first is syntactically incorrect— “redundant” parentheses being needed for the data list and for the iteration specification. Such syntactic nonuniformity definitely discourages semantic explora- tion, for it renders the programmer uncertain about his power with the language. For instance, having encountered these two exceptions to the parentheses rules, the programmer may be reluctant to put parentheses in a complex expression to clarify its meaning, or he may feel impelled to put in extra parentheses because he has to “make sure.” In general, he is going to be confused, and programmers should nat be confused about their programming language. As an interesting sidelight on the uniformity question, we might note that this trouble over parentheses in PL/I stems from the overuse of parentheses required because of the limited character set. This effect of limiting the character set is quite general, because any time the same character is used in two different contexts, you have nonuniformity. APL probably goes the furthest of any current language in the exploitation of a large character set, even to the extent of using a different symbol for the operator ‘‘minus” and the ‘‘minus sign’ of a number, and supporters of APL claim that it is a most powerful language because of this uni- formity. Such statements, of course, should be subjectable to experimental test. As an example of how such a test can be made, recall the long paren- thesized statement of Figure 3-1, which in PL/I! had six levels of par- entheses. This statement can be recoded in APL, as shown in Figure 12-2. To the casual eye, this statement does not appear as complex as the one in Figure 3-1, partly because of the two types of brackets, and partly because two pairs of parentheses can be eliminated because of APL’s function form and right-to-left rule. Is this casual observation verifiable? We studied the question by making several variations of the two statements which were ill-formed and testing the ability of pro- INGLES[I]+2xATAND SORT (S-ACIND(I311]1)x(S-ALIND(I;21])+#Sx(S-ACIND(I;3]]) Figure 12-2 An APL version of Figure 3-1.

222 Programming Tools grammers experienced in PL/I and APL to identify the ill-formed versions. There was a definite edge in both speed and accuracy to the APL state- ment, which seems to indicate that APL’s claim to uniformity is at least partially well founded. The limitation of the character set is but one case of machine limita- tions leading to failure of uniformity. The restriction of FORTRAN sub- scripts to another example, or at least an example of failure of nerve on the part of the implementers, who thought they could not give efficient code if subscripts were unlimited. Another source of nonuniformity is the quest for certain other design principles, not all of them well founded. The deepest of these principles go unchallenged, particularly the assumption of unambiguity. Even so sophisticated a writer as Bryan Higman Says: “Because every language must have a character set and an unambiguous, decidable grammar... ,’’ but he leaves the assumption of unambiguity unchallenged. Any assumption that deep should be challenged once in a while, just to keep it healthy. A nice example of the obsession with unambiguity is the syntax of the WHILE clause in PL/I. Following the keyword, WHILE, is a logical condi- tion, as is found following the keyword IF in an IF-statement. However, in the case of the WHILE but not in the case of the IF, parentheses must surround the condition—a nonuniformity which leads to a number of errors in syntax. The reason for the seemingly extra parentheses is that a syntactic ambiguity can arise if the programmer has declared an array named WHILE and writes a statement such as DO WHILE(I) = 1; For this one case, every programmer using a WHILE must use an extra pair of parentheses. Actually, even with the morbid dread that programming language de- signers have of ambigiuity, it creeps into all languages. But the am- biguity that creeps in is, in most cases, psychological ambiguity—which seems to be beneath their consideration. A classic case is intermixed multiplication and division, as in A = B/CD Although the language designer and the compiler do not regard such a Statement as ambiguous, programmers certainly do—as evidenced by the number of bugs we find related to this type of situation. What needs to be done about ambiguity is to recognize that there are two types—physical and psychological. Compilers, of course, are never ambiguous—one may interpret the above statement as A — B/(CD) while another may interpret it as A = (B/C)*D but each will be consistent in its interpretation. Because of possible

Some Principles for Programming Language Design 223 different interpretations by compiler writers (which is really psychological ambiguity in the end), it is important to eliminate this kind of ambiguity from a language so that all implementations will be no farther apart than necessary. But eliminating physical ambiguity does not solve the psychological problem. Ken Iverson, the inventor—the developer—of APL, once told me that APL’s “right-to-left” rule for resolving ambiguity was “more natural’’ than the rules used, say, in PL/I. Certainly the strict right-to-left rule is simpler to . state than the PL/I rules (which in a formal sense often leave the order. of execution undefined anyway), but its “naturalness” is an open psy- chological question certainly worth exploring as a way of resolving ambiguity. From an introspective point of view, | can say that | have never entirely come over to a right-to-left way of thinking, after writing perhaps 400 APL programs. Certainly my students have not come over after writing a few dozen, and the psychological ambiguity remaining from their early habits with left-to-right and other rules remains one of the greatest, if not the greatest, single sources of APL errors | see. For myself, | have come to the adaptive reaction of overparenthesizing (even in PL/I) to avoid possible psychological ambiguity. There is some ray of hope on the horizon that programming language inventors realize the difference between these two kinds of ambiguity. Klerer and May have taken the interesting approach of permitting the user to write in a way which is natural to him, and then to give him a message which says: THIS IS THE WAY WE INTERPRET YOUR STATEMENTS. IF ANY ARE INCORRECT PLEASE RETYPE THE STATEMENT CORRECTLY. Such an approach becomes particularly appealing in a terminal situation, where we at last get something approaching the dialogue of a natural.: language. It requires that we revise our static notion of what is a»: “language.” This approach would be especially useful for such rare ambiguities as the PL/I WHILE. If complete unambiguity is desired, why not simply make a secret rule in the language that is applied only when the pro- grammer uses an array named WHILE. In that circumstance, the com- piler can tell him to change such a WHILE-clause should it appear, and the rest of us need not bother. In practice, of course, compilers recognize this situation and warn us that they have inserted the parentheses for us. Why not dispense with the warning, and only punish the programmer who is stupid enough to use keywords as variable names? Which leads us to our final point about uniformity. Not all of the blame lies with the language designers—individual programmers can be far more guilty of this sin. For example, choosing keywords as variable

224
Programming Tools
names means that the name is used in a nonuniform way in the program,
which certainly confuses the task for anyone trying to read the program.
(1 have been particularly guilty of this in some of my examples in earlier
books.) Other sources of nonuniformity are declaration of variables with
different sizes for no particular reason, use of abbreviations sometimes
and full names at other times, arbitrary indenting and spacing of the
source program, and commenting densely in some places and not at all
in others that seem more difficult.
But the major source of programmer nonuniformity remains the choice
of mnemonic names. Abraham Lincoln used to ask this riddle: “lf we
call tail a leg, how many legs does a dog have?’’ When the respondent
could answer
“five,’’ Lincoln would admonish,
“‘No, four. Calling
it a
leg doesn’t make it a leg.”” How many times—how many thousands of
times—have we seen a program in which OLDX has not been assigned
the old value of X, SUMAB has not been assigned the sum of A and B,
or BIGGEST has not actually been given the biggest value? Calling it
OLDX doesn’t make it the old X; but no matter how often we deliver this
admonition,
the fallacy seems
to perpetuate
itself. Just as with non-
uniformity in the language itself, this practice hurts us just because it
leads us to believe something that is not true.
From
a debugging standpoint,
a slight amount
of nonuniformity
is
really worse than a great deal, for the general uniformity
lulls us to
sleep.
If most of the time, OLDX js the old value of X, we are more
likely to miss the one time that it isn’t; and if most of the time (A) means
the same as A, we may never find the bug in our program.
COMPACTNESS
We have already mentioned the principle that the human mind has
certain inherent limitations in capacity. Although these limitations vary
from person. to person and from one sort of material to another, we
can safely say that for a given person, a short program will have a
tendency to be more easily comprehended than a long one. Our studies
of comments seemed
to indicate, for instance, that even the addition
of nonexecutable text tended to increase the difficulty in reading a pro-
gram—even when that text was added explicitly to make the program
more readable.

The compactness of a program, however, is not measured merely by counting the number of characters needed to express it. Although the number of characters is important when we are trying to account for such factors as keying errors, the critical unit may generally be somewhat larger than one character. For instance, the expression, A + B,

Some Principles for Programming Language Design 225 is not one-third as difficult to understand as the expression, BIG + SMALL. The reason it is not is that we rarely use single letters as our funda- mental units of thought processes: we are in the habit of using “words.” Many experiments have shown that a word such as BIG can be handled just as readily as the word A. In psychology, this information processing ability of human beings that combines several small units into one large unit, which is just as easy to handle as its individual parts, is called chunking. Chunking is a recoding process, one which we usually do without conscious effort. Nevertheless, even though the process of chunking is something we do quite naturally, each individual chunk has to be learned by experience. When we first begin to encounter such binary strings as 110010111100, we have to remember them as twelve separate characters. After a while, however, we begin to learn octal or hexadecimal, both of which are more or less formal systems of chunking. Soon, we begin to “see” this string im- mediately as 6274 or CBC, so that the length of any program containing it becomes “shorter.” Some program chunking is a natural ourgrowth of chunking learned elsewhere, as in the recognition of such ‘words’ as DO, READ, and AL- LOCATE. Other chunking we begin to learn the moment we first set pencil to a coding pad, so that at different stages in a programmer’s experience with a language the same program will have different lengths. To an experienced PL/I programmer, the statement DO !|!=1TON; can probably be taken in at a single glance. The novice, however, might have to break it into six or seven smaller chunks. Although chunking tends to make commonly used character sequences seem shorter, the programming language designers are limited to taking advantage of the chunking which they can assume the programmer brings to the language, or to that which he just happens to learn as he uses the language. By providing the user with alternative ways of expres- sing the same thing, however, the language designer gives the program- mer a chance to do another kind of compression. The simplest example of this type is the provision of abbreviations for keywords, such as PROC for PROCEDURE in PL/I. In natural languages, there is an empirical ob- servation known as Zipf’s law, which says that the most frequently used words tend to be the shortest words—words such as “is,” ‘‘the,” “a,” “I,” “he,” and “it” in English. We also see the operation of Zipf's law in the creation of contractions, such as “don’t” or ‘‘there’s,” but the most common operation is probably concealed in the long dead past. In programming, however, we can often see the tendency to this type of compression in action, as when a programmer begins to write PROC all the time instead of PROCEDURE. He may never learn the abbreviation

226 Programming Tools for seldom used words such as STRINGRANGE, even though it may be much longer than some word he does abbreviate. Other abbreviation occurs through the substitution of more powerful functions for less powerful ones, as when the programmer learns to write SUM(X) instead of a DO-group, or a DO-group for a list of cases written out se- quentially. Iteration, of course, is the most basic of the compression steps possible in programming—it was known to Lady Lovelace, and without it modern computers would essentially not be possible. Functions —such as SUM—were also a concept known to our first programmer and, although they are not conceptually as important as iteration, they are probably more important from a psychological and practical point of view. The language designer, of course, can provide either built-in functions such as SUM and SQRT or facilities for the programmer to make his own functions. Probably no modern programming language lacks both of these general features, but there exist great differences in the details of what is actually provided. One particular limitation on the use of programmer-defined functions is that the programmer is seldom given the facilities to duplicate what the language designers can build into their functions. For instance, PL/I has many powerful subroutine-defining features, but the programmer cannot yet provide such things as full generic capability for his routines, even though that ability exists in the built-in functions. Another source of compactness—newer in use, but perhaps ultimately more powerful than iteration or subroutine definition—is data structuring. In APL, for instance, most operations apply in uniform ways to arrays of all sorts as well as scalars. In PL/I, many array operations are possible, and there are also structure operations. As an illustration of the power of these operations to permit many actions to be specified as one, con- sider the OLDX problem we raised earlier. If, instead of declaring an OLDX, OLDY, and so forth, we had declared a structure such as 1, OLDA2:0G WV Fae) we would have been able to execute a single statement OLD = NEW; which assured that a// the old values were replaced by all the new ones. Many special-purpose programming languages draw their power from the higher-level data structures they provide, structures not yet found in multipurpose languages. Where these structures are in consonance with the user’s needs, the resulting compression can be manifold. Where they are not—as when we try to do simple arithmetic operations in LISP or SNOBOL—the data structures have just the opposite effect. The most promising escape from this dilemma is through embedding special data Structures in a multipurpose language such as PL/I. PL/I does provide a number of features which assist the programmer in creating data-structure-

Some Principles for Programming Language Design 227 processing systems not built into the language, although the inability to replicate the general abilities of the language itself is once again an impediment. Another way in which appropriate data structures lead to compression is by causing lower-level operations to have higher-level results. For instance, if we are processing a sorted file in sequence, the assumption that the records are in order permits us to make inferences about missing records which would otherwise require elaborate programming. Over- laying of one structure on another is a further case of promotion of operations—as when we set all bits in a string by setting the whole string, rather than processing them one at a time. The difference between this sort of operation and the operation of implied repetition through array and structure expressions is really in the relationship between what the language specifies and what the computer on which the language is implemented can do. Given the appropriate machine, array processing can be done directly, too, just as we now do the assignment of strings. Before leaving the subject of compression, we must scrutinize the other side of the coin for a moment. Perhaps the ultimate form of com- pression is the elision of certain cases entirely—usually the most frequent ones. In PL/I, we write SKIP instead of SKIP(1), since that is by far the most frequent case. When declaring a variable, we get many attributes by default, so that when we write DECLARE xX; we get the equivalent of something like DECLARE X FLOAT DECIMAL(6) REAL AUTOMATIC INTERNAL; Indeed, in many cases the declaration can be left out altogether, causing X to be declared implicitly or contextually. The difference between these extremes is much like the difference between Chinese and English grammar. English has many redundancies, as when we say He went two time(s). The plural ending is redundant, in that plurality is clearly specified by the ‘two,’ and in Chinese, there would be nothing to correspond to this extra letter. Such redundancies are quite useful in normal speech trans- mission, for they help in catching the precise meaning of the utterance even though there have been distortions in parts of it. In writing, however, they seem more of a burden than they are worth, so it seems worth- while to have a sensible default system in any programming language. The ability to use such a default system, however, depends on the existence of a limited universe of discourse, or a least a universe of discourse which is statistically limited. That is, the default case does not help us much if it is used only a small fraction of the time. As there are more and more possible cases, the burden of remembering which is the

228 Programming Tools default can become greater than simply writing the complete specifica- tion. If the cases occur with more or less equal frequency, the default system then loses its value because it cannot be used often enough to make it worthwhile. On the other hand, if, the defaults are used often, we may not remembe; the nondefault cases when we have to use them. Although we have not studied this situation empirically, we might con- jecture that defaults are most useful when they are used fairly frequently, but not too frequently. Of course, the above principle applies to the psychology of a particular programmer using the language. Defaults may be very useful when sev- eral different types of programmers use the language. For instance, the default of REAL is needed only in contrast to COMPLEX, a feature which perhaps nine-tenths of PL/I programmers will not even know about, let alone use. Defaults such as these serve to produce different languages from a single language, for, in effect, the nine-tenths who do not know about COMPLEX are programming in a language that cannot handle com- plex arithmetic. This type of automatic subsetting through defaults, however, will not work unless many details are accounted for. For instance, in PL/I, the built-in functions are generic, in that they adjust their operation according to the type of data passed to them. If we perform SQRT(X), the operation is different for X REAL than for X COMPLEX. For REAL X, X=—1 will Cause an error condition to be raised; and for COMPLEX X, the result will be 11. But why not yield 11 when X is REAL? Consider what would happen to the unsuspecting programmer who writes A = SQRT(X); without knowing about COMPLEX at all. If 11 were the result on the right- hand side, A would be given the value zero, for 11=0+ 1I, so that only the real part would be assigned. Such a trouble would be most dificult for the programmer to find if he had no knowledge of complex arithmetic, so he must be protected if the default is to help him work in a true subset. By eliminating redundancy in an appropriate way, then, we can reduce tedium and error. In the limit—in a programming language from which all possible redundancy were eliminated—it would not be possible to have any syntax checking of the program, for every possible string would be a syntactically correct program. An example of such a language is the machine code for certain systems, where all op codes are permitted and all address specifications are legal. We know that machine language pro- gramming can be extremely difficult—is it possible that this difficulty is due to lack of redundancy being carried too far? To answer such a question, we must make a clear distinction between psychologically useful and psychologically useless redundancy. As a case in point, consider the FORTRAN expression, A(1+ 1). If our FORTRAN

Some Principles for Programming Language Design 229 prohibits this string, the compiler will signal an error. Not all strings are permitted, so error checking can be done by detecting illegal strings. But what is the compiler really checking here? The rule prohibiting A(1 + 1) was not set down for the psychological comfort of the program- mer, but, rather, to make it easier to compile ‘‘optimum” code. Although it is true that this expression is in error, the error has nothing to do with our problem, but only with FORTRAN’s restrictions. In a sense, it is not the programmer, but the language, which is in error. LOCALITY AND LINEARITY In an earlier discussion, we gave an example of how memory helps a programmer by enabling him to keep the relevant information in his head even when he does not have the program in front of him. A properly designed programming language can help the programmer in the same way that a good memory can—by keeping the relevant information close at hand. . Two types of human memory that interest us in this regard are the so-called “synesthetic” and “sequential’’ memories. Synesthetic memory is the faculty that enables us to recognize a face, a neighborhood, or the layout of a page in a book—without recourse to specific details. The word itself is derived from the Greek—‘“syn” meaning ‘‘together,” and “esthetic’’ meaning ‘feeling’ or ‘‘sensing.’’ Our synesthetic memory, then, is that part of our memory that enables us to remember things as a whole, all taken in one glance. Sequential memory may be more related to auditory things, as when we can whistle the tenth bar of a tune after hearing the first nine bars, but not if it is asked for alone. The sequential memories we have seem to be constructed like unidirectional chains—like speech, if you like—chains in which each link’s appearance triggers the memory of the next. We exhibit sequential memory when we recite a poem, remember the route to a cer- tain place as we traverse it, and sometimes when we memorize a list. In a program, the concept of ‘locality’ corresponds to synesthetic memory, and the concept of ‘linearity’ corresponds to sequential memory. By locality, we mean that property that obtains when all relevant parts of a program are found in the same place—on the same page, perhaps. Without good locality, a programmer working with the listing of a program has to be turning pages constantly—unless his syn- esthetic memory is sufficiently good to remember the material on the other pages that is relevant to the current page. One way that a programming language encourages locality is through compression, that is, if the entire program fits onto one page, all relevant parts are obviously on that page.

230 Programming Tools Locality is also encouraged or discouraged in other ways. The ability to use literals is a big help, because all that we need to know about a literal is contained within its name. The use of well-chosen mnemonic names can have the same effect, if it helps the reader to remember what he needs to know about the variable without referring to the place it is declared. Of course, if a variable is only used locally, it is helpful if the language permits us to declare it right at that location. In some languages, however, usually to enable the compiler to make one less pass, all declar- ations must come before all other parts of the program. In such cases, reading a long program involves constant flipping back and forth to the first page. Yet even when the language requires all declarations to be given in one place, a certain locality is obtained, for we can always tear off the first page and keep it alongside whichever page we are currently using. Indeed, some people complain about languages such as PL/I, which give great freedom in the choice of where declarations are made. This free- dom, like any other, is often abused by programmers who do not under- stand why it is given. Ideally, a program written in PL/I should have all global variables declared together on one page—at the beginning or end —and all local variables declared within the section of coding that uses them. As an example of the type of trouble that nonlocality causes, consider the typical situation arising from such a loop as DO | = 1 TO 10; PUT LIST(I,F(l)); END; where F is a programmer-defined subroutine, such as F: PROCEDURE(J) RETURNS(FLOAT); S.=0 DOs: HO ths S=S+4+]; END; RETURN(S); END F; Because the variable | is not made local to the subroutine, we will find that the main loop only puts out five pairs of output values: hs seal 3 6 Sua S 7 28 9 45 instead of the ten the programmer probably expected. He expected ten values because he thought that | was local to his DO-loop—that nothing he could not see right in front of him would affect |. But, as it happens, | is used inside of F and not declared inside of F, so | changes each time F is entered. (Recall that when the DO-loop

Some Principles for Programming Language Design 231 DO!=1TO J; is finished normally, | has the value J + 1.) Typically, this sort of bug causes great difficulty, for when we are not able to find a bug, it is usually because we are looking in the wrong place. In this case, we are looking in the wrong place because we assume —as is quite natural to do—that only what we see at the moment in front of our nose is what affects the value of |. In whichever programming lan- guage we use, we must learn, when debugging, in what ways nonlocal effects can manifest themselves. For example, in PL/I, subroutine calls (through global variables or through arguments passed ‘“‘by address’’), ON-units (which are similar to parameterless subroutine calls but can be invoked without any local mention of the ON-unit at all), and defining of one variable on another can lead to effects that are not to be found by looking at the place in the program where they seem to be arising. Features of a language that can lead to nonlocality are not usually there just because of poor language design; otherwise they could be eliminated and nobody would mourn their passing. Defining one variable on an- other, for example, is provided so that compression can be achieved— which in itself can help locality. Therefore, there is a limit to how far the language can go in preventing nonlocality from arising from poor pro- gramming practices or mistakes. In the example given above, however, it might have been a better decision on the part of the PL/I language de- signers to have variables, such as | or S within F, be automatically local to F unless the programmer made explicit declaration to the contrary. Of course, if he makes any kind of explicit declaration of | or S, as in DECLARE | FIXED BINARY, S FLOAT; the declaration will restrict their scope to F, as long as it is within F. Thus, the programmer can overcome this difficulty by always declaring all variables, even if he only writes DECLARE Il, S; The ON-unit of PL/I represents a particularly interesting case of non- locality, because the basic function of ON-units is to permit us to obtain linearity. In fact, in the sense that the ON-unit is ordinarily used to handle an exceptional case—one that may never arise in some programs—it can be thought of as a way of achieving additional locality. In the first place, by not forcing us to mention the condition by name, PL/I permits us to achieve greater compression in the main body of code. Secondly, a statistical compression occurs because the exceptional case is taken out of our sight when we are considering ordinary cases. Thus, the ON- unit permits us to forget about exceptional cases, which simplifies coding —but which exacts a price when we need to remember the ON-unit. But the ON-unit helps us to achieve linearity, which aids our sequential memory. Experiments with problem solving in programming-like situations

232 Programming Tools indicate that a series of decisions arranged in a strictly linear sequence is typically easier to handle than a branching or looping sequence. Ex- perience with programming languages seems to bear this out, for pro- grams with numerous GO TO statements or other branches are notori- ously difficult to understand or debug. Part of this difficulty comes because of simple nonlocality, for the branch address may be on a differ- ent page—and in a 50-page program, this type of branching can cause a lot of page turning. Confusion can arise, however, even when the branch address is on the same page as the branch. For one thing, it may be that some unnoticed branch also leads to the same point, so we cannot easily figure out how we got where we got in execution. For another, each branch breaks our normal sequential mode of scanning the program or thinking about it. Even though we don’t turn a page, our natural progression has been interrupted, making error more likely. Many higher-level language features can help us mask the nonlinearity of the typical stored-program machine. The IF statement, which allows the statement to be executed to be attached to the IF; the DO or BEGIN, which allows us to group several statements as one under the THEN or ELSE clause; the logical connectives, which enable us to group decisions under one IF statement; the iterative DO, which permits us to eliminate explicit branching; the array operations, which enable us to eliminate explicit iterative DO loops; the ON-unit, which permits us to handle the exception condition without even mentioning it, or even thinking about it—all these and other features help to banish the nonlinearity from a program. Indeed, in PL/I and other languages, many programmers regard the presence of a statement label for branching to be a mark of poorly constructed code. Although we might not want to go that far, it does seem true that the GO TO is the one “major” statement that could be eliminated from PL/I with the least loss to the programmer. TRADITION AND INNOVATION One of the most important ways that a programming language can achieve ease of expression with a minimum of error is by being “natural.” One way of achieving naturalness, as we saw, was through uniformity, but uniformity only applies to those programmers who have some ex- perience with the language. When we begin to use a language, we are not able to notice the uniformities—instead we notice those things in the language that do not seem to correspond to our preexisting sense of “rightness.” Where do we get this sense? Probably, it comes from two sources—the natural languages—English, French, Chinese—and the pro-

Some Principles for Programming Language Design 233 gramming languages—FORTRAN, ALGOL, COBOL—which we know be- fore we start to learn the new language. By making a programming language in some sense consonant with the other languages the user is likely to know, we may simplify his entry into its strange new world. As an example of the natural language concepts we bring to a pro- gramming language, consider the case of the significant blank. In the orthographic forms of the European families of languages, at least, the concept of the significant blank is quite natural. In FORTRAN, however, there is no such thing as a significant blank—except within a literal string. Moreover, the concept of ‘‘multiple blanks equal one blank” is natural, up to a point, as is the concept of a delimiter or a blank. The word ‘“‘orate”’ is not the same as the two words, “or ate.” Elision of the blank, however, would usually be correctible, since some grammatical or semantic vio- lation would occur, due to redundancy, but not necessarily, as in, “He gave it to you, or ate it.”’ “He gave it to you, orate it.” When the elided blank changes the sentence to a meaningless one, the reader will usually be able to correct it, but almost always with the con- scious insertion of a blank: “He lost it or ate it.” “He lost it orate it.” The blank next to a delimiter, however, can be left out without usually being noticed, as in “Leave it alone,will you!” When the programming language is not in consonance with these natural language rules for blanks, it may be more difficult to learn, or at least more difficult to debug. In FORTRAN, for instance, there is the classical type of bug which is typified by the statement DO 33 | = 1... 20 Here, the comma has been changed to a period—perhaps by keypunching —with the result that the entire statement is interpreted as DO331=1.20 The blanks here fool us into thinking that DO,33, and | are three separate entities, but they actually form only a single variable, DO33I, under FORTRAN rules for blanks. A much more costly mishandling of blanks arises in the syntax of the job control language for OS/360. Here, the operand field—as in assembly language—is terminated by the first blank. This strange convention was used to permit comments to follow the operand field, as is done in the System/360 assembly language. But simply carrying this idea over from the assembly language—though consonant as far as the OS/360 designers were concerned—proved to be a poor choice for other users. First of

234 Programming Tools all, FORTRAN, COBOL, and PL/I! users were not familiar with the con- vention. Secondly, nobody really wanted to use comments on job control the way they did in assembly language. Finally, the frequency of con- tinuation cards in job control usage was much greater than in assembly language, where continuation only arises frequently in the use of macros. A typical mistake caused by this unthinking design carry-over might be the following: //XX DD DSNAME=ABC, DISP=(NEW,KEEP), C The programmer, in punching, unconsciously leaves a blank after the comma following ‘ABC.’ He does not realize this blank because in English, or FORTRAN, it has no significance. The computer notices it, however, and thus takes it to be the beginning of the comment field. Consequently, the DISP parameter is ignored entirely, and the programmer finds that his data set is not saved, since DISP=(NEW,DELETE) is assumed. Much worse cases can be constructed, and it is difficult to estimate how many millions of dollars of machine time and programmer time have been wasted because of this design error. In effect, of course, it is not a design error, for the job control language was not really designed, in the sense that PL/I, FORTRAN, or COBOL were designed. Being part of the operat- ing system, it was not thought of as a “programming”’ language at all, so somebody just carried over a technique which was fairly satisfactory from the nearest available source—with disastrous results. (See also Figures 10-1 and 10-2.) Another area in which programming languages could be more con- sonant with natural languages is spelling. When we read natural language text, we often pass over misspellings without notice, even when they make nonsense words. In speaking, even greater flexibility is allowed, for we can understand the Atlantan when he says “pie,’’ the Omahan when he says ‘milk,’ and the Bostonian when he says “yard.” Programming languages, however, are often pedantic to the extreme when it comes to spelling, even in cases where what was meant could have been perfectly clear. Abbreviations, of course, are alternate spellings; but the concept could be carried much further—even to the point of making guesses—as many commercial systems now do—as to a correct spelling. Spelling, of course, is only one example of a more general concept of “looseness” found in natural language. We can say either “Bring it here, will you?” or “Will you bring it here?” | and nobody will notice the difference. Programming languages, however, rarely are as flexible as natural languages, although we do have some leeway. Noise words in COBOL are one example of flexibility which per- mits the programmer to achieve some additional feeling of consonance with English. In PL/I, there are a number of places where reordering of

Some Principles for Programming Language Design 235 part of a statement is permitted, as in DO!=1TON BY K; DO!=1BY KTON; or DECLARE A FIXED BINARY; DECLARE A BINARY FIXED; or PUT SKIP LIST(A); PUT LIST(A) SKIP; By permitting two possibilities where only one might have existed, the language should be easier to learn, for each user can choose the form easiest or most natural for him. He may not, however, be conscious that he has made a choice. He might never have had the alternative form presented to him, possibly because his teacher prefers the other one. Or, he may get the impression that there is some semantic difference between the forms, and so be afraid to use one once he feels he knows the other. And, indeed, there might be semantic differences, although these might be as slight as to be unnoticed in the ordinary programmer’s lifetime. The two statements DOI|I=1TON BY K; DO!|!=1 BY KTON; are not quite precisely the same in PL/I, nor are the expressions (A=2)&(B=3) (B=3)&(A=2) Aside from the psychological differences—from one person to another, or between reading and writing—there are possible performance differ- ences which, by always lurking in the background, may discourage the programmer from taking advantage of the looseness available to him. In the same way that some differences within a language are significant and some are not, so too are some differences between languages in- significant—or at least of so little significance as to not be worth the bother of trying to preserve. For instance, it may make some difference initially whether our language requires DO |1=1 TON BY 3; or FOR |: = 1 STEP 3 UNTIL N; but any capable programmer should be able to move from one to the other with an absolute minimum of difficulty. Indeed, the difference be- tween them is so insignificant that both could easily be permitted to exist in the same programming language. In arguments about the relative merits of programming languages, however, we often find the most heat about those points where there is the least significant difference, for it is only at those points that the two languages can be directly compared. Perhaps someone will someday perform an experiment to see whether DO or FOR is the ‘‘best” keyword for this situation—but there is little chance that mere facts would disturb such an argument. When we design a new programming language to be similar to another commonly used one, we can definitely speed up early learning by re- maining within the earlier tradition wherever possible. We pay for this early learning in an interesting way, however, for no two programming languages are exactly alike—otherwise they would be the same language.

236 Programming Tools If the languages are slightly different, then the psychological phenomenon of inhibition will occur. Inhibition comes in two forms—retroactive and proactive. Proactive inhibition occurs when the similarity of earlier learned material interferes with the learning of later material. Thus, for instance, FORTRAN programmers have an inordinate amount of difficulty learning to use format items such as F or E in PL/I, because, although they are similar, PL/I uses a comma where FORTRAN uses a period. When we look at the PL/I programs of a FORTRAN programmer, we often find such format items as F(10.2)—the results of which never fail to confound the FORTRAN programmer. It is the similarity between the languages which causes the inhibition, as can be seen by examining the use of picture formats by the FORTRAN programmer. Such PL/I beginners have little or no difficulty learning to write P’SSSSSSSV.99’, because there is no comparable structure in FORTRAN. (But, of course, COBOL programmers do have trouble.) The retroactive inhibition also depends on the similarity, but it works on the previously learned language. Thus, typically, the FORTRAN program- mer who has learned PL/I finds it difficult to go back to writing FORTRAN programs—and not just because some time has elapsed. Inhibition, therefore, is the price we pay for making two languages similar but not identical in some feature. It might be better, when identity is not possible, to make the two more clearly dissimilar, so that inhibition will have less force. But the cost of inhibition is not the only price we pay for trying to make one programming language match an earlier one. The old structures simply may not fit in with the new things we are trying to do, in which case the overall unity of the language will suffer. In this case, early learning slows down later learning—learning of new and powerful techniques. Alternatively, we may not be able to fit the new techniques to the old language, so that trying to achieve consonance may wind up achieving only sterility. Because individual programmers are so different from one another, there is a definite limit to how far we can make our language consonant with the ideas and structures the programmer already knows. To be sure, we can pretty well count on all programmers recognizing such forms as numeric literals and simple arithmetic expressions, but we soon run into areas where different people prefer different ways of doing things. Looseness is one way a programming language allows for individual differences—by permitting a selection among two or more built-in things. Thus, in a sense, a loose language can adapt to preferences of individual programmers. But there are other ways a language can be adaptable. These ways are Characterized by the creation of new things, some for a single program and some to be used across a whole class of programs. One familiar ex-

Some Principles for Programming Language Design 237 ample of such adaptability is the ability to define functions and other subroutines. Another is the ability to define new data types—structures and lists, for example. Some languages permit the definition or redefini- tion of operators, but these are, after all, just another type of function. Finally, there are languages with more elaborate metalanguages—com- pile-time facilities, for instance, or dynamic generation of code at ex- ecution—which can permit changing the entire face of the language. When we use these facilities for adaptation, we may be able to gain a degree of consonance with our idiosyncracies which cannot be ap- proached by the language designer directly. But in getting closer to our own specific modes of thought and expression, we may easily get further and further from the modes of other people. Programming is not just communication between one man and a machine. Other people will prob- ably have to read and understand the program; but if the use of adaptive devices is too extensive, the program may be as closed to them as is the private language of a schizophrenic. Within an installation, a certain amount of adaptation may be advan- tageous to adjust the language to the precise needs of the local type of work. Of course, at some point this type of redefinition becomes the design of a new language—a job not to be undertaken lightly by amateurs. If the language being used is really so far from the needs of the installa- tion, it might be better to search for a language that is closer to the needs before modifications are made. There certainly are enough lan- guages to choose from. SPECIAL-PURPOSE, MULTIPURPOSE, AND TOY LANGUAGES To some extent, our discussion of languages has been biased toward the languages of the professional programmer, with comments on the needs of the amateur. But the principles involved are deep ones, and they apply to amateur languages as well, although with modified empha- sis. If we design a special language for a particular application area, we start immediately reaping psychological rewards by limiting the uni- verse of discourse. Thus, a statistics language does not have to have facilities for complex arithmetic, a string processing language does not have to have much arithmetic at all, and a machine-tool control language might have more of a geometry than an arithmetic. Sometimes, however, these limitations are imposed simply by limiting the capacity, under the assumption that problems in that field never get any larger than a certain size. Thus, a simulation language might have no facilities for using

238 Programming Tools auxiliary storage, which means that many language features can be dropped entirely—but it also means that certain problems cannot be simulated. Limitation of the universe of discourse is so important for the design of special-purpose languages that it should be the first and most carefully considered step. Much of the success of APL, for example, had to do with the careful limitation of the universe with which it could deal, al- ‘though most APL programmers think of it as a “multipurpose” language. “But simply by limiting the language to terminal situations, to relatively small workspaces, and to no auxiliary files, the designers were able to provide a problem-solving tool par excellence—for problems within its scope. The success of the language can be attributed to the existence of large numbers of people whose problems fall within that limited scope— or at least fall within it while they are learning APL. Programmers who progress to, say, data-processing problems, in which large quantities of data are processed, soon find they have ‘‘outgrown’’ APL. Much of the enthusiasm for the system, however, doesn’t take cognizance of the limitations that are built into it and account for a good deal of its success. One reason why the limitations are not as obvious to users as they are to nonusers is that the programming language does indeed shape thought processes. We are not speaking here of simple inhibition effects, but of larger effects on the organization of programs and data. | became aware of this effect most clearly when teaching a beginning PL/I class composed of COBOL, FORTRAN, and Basic Assembler Language pro- grammers, and people with no programming language at all. As | was reading the programs handed in for the first assignment, it struck me that | could conjecture the background of the programmer, and | re- corded my guess for each one. | did not have any background sheets on the programmers, so there was no external way in which | could know their experience. The next day | checked my guesses with the class and obtained the results shown in Figure 12-3. To be sure, a number of guesses were made simple by specific feature clues, such as the use of a decimal point instead of a comma in an F- format item. But these specific clues also led me to make several wrong guesses: | guessed “FORTRAN” for a BAL programmer because he used such decimal points, and “COBOL” for an inexperienced program- mer because of the way he used symbols such as PART_COST, with the underscore standing for the COBOL hyphen. (It turned out that he had seen one of the COBOL programmers doing this and copied the style.) As a consequence of the influence of language on thought, the problem that one will attempt to solve is limited by the programming language one knows and how much of it he knows. Therefore, there tends to be a self- fulfilling satisfaction with a special-purpose programming language, as

Some Principles for Programming Language Design 239 Figure 12-3 Guessing programming background. well as with one’s current level of knowledge. To the extent that special- purpose languages act as limits to thought in this manner, they are harm- ful to the user who potentially has larger problems to solve. The situation with COBOL is particularly instructive. People have lost sight of the original intention of COBOL’s designers. To quote one of them, Jean Sammet: The users for whom COBOL was designed were actually two subclasses of those people concerned with business data processing problems. One is the relatively inexperienced programmer for whom the naturalness of COBOL would be an asset, while the other type of user would be essentially anybody who had not written the program initially. In other words, the readability of COBOL programs would provide documentation to all who might wish to examine the programs, including the supervisory or management personnel. Little attempt was made to cater to professional programmers... . The COBOL designers went to considerable trouble to see that these objectives were actually met. One result of their efforts for instance, is that programming managers in COBOL shops do seem to read programs somewhat more than do managers in other types of groups. Outside of the programming managers, however, it is doubtful whether many managers do read COBOL programs, or read them with any good effect.

240 Programming Tools However, the fact that COBOL was successful in getting nonprofes- sionals to writing programs in the sixties is proving to be an impediment to progress for the seventies. A crew of COBOL programmers is not, in general, an asset upon which an installation can build, say, on-line, fast response systems. Even the transition to effective use of random access files has been impeded by the sorting mentality that COBOL imposes. Seventy-five percent of the COBOL installations with which | have con- sulted on operating systems problems were essentially using their random access files as sequential devices, at best to get faster sorts, and at worst to get slower sequential processing than with tapes. Whether COBOL and COBOL users can grow with the expanding facilities available to them is an open question at the moment. There seems to be no way out of this paradox—the better job a pro- gramming language does for the special purpose for which it was de- signed, the more limiting it is on the minds of those who use it, if they are faced with potentially new areas of application. In fact, this is another example of Fisher’s Fundamental Theorem of Natural Selection. The professional programmer cannot afford to get into the trap of being so adapted that he is not adaptable, but we don’t necessarily know in advance who is destined to become a professional. Only by studying the ways in which special-purpose languages adapt to their special circum- stances do we have any hope of understanding what will be involved in reshaping thought processes if it is necessary to create a professional from an amateur or semiprofessional. Special-purpose languages are also successful in exploiting special data structures which correspond to the user’s needs in his special area, simultaneously achieving compactness and correspondence with previous learning. We would expect, for example, that a language to be used in social science research would provide for arrays of data such as ques- tionnaire results, or that a language for doing algebraic manipulations would have standard data forms for polynomials or other symbolic ex- pressions. And, indeed, they do, which accounts for a large part of their success—or failure, if they fail to be the right structures. Along with special data structures, we find special routines for doing familiar processes on those structures. Thus, our social science research language would undoubtedly have various tabulation and data reduction primitives, and the algebraic language would have such primitives as analytical multiplication, differentiation, and simplification of expressions. Of course, the names of these functions and structures will be chosen to give the user the feeling that he is working in familiar territory, so as to take advantage of pre-existing learning. In a special-purpose language where the main users will be non- programmers, the use of pre-existing learning is a major design objective,

Some Principles for Programming Language Design 241 lest the user turn away from the system before he really has a chance to use it. Unfortunately, many of the special-purpose languages that have been invented—I cannot really bring myself to say ‘“developed’— have only a correspondence with pre-existing learning to offer. A person is able to use them quickly enough, but soon finds that he cannot solve his real problems, but only “toy” problems resembling his real problems. Even when the language is theoretically able to describe his problem, the system may be so out of correspondence with the internal realities of computing that the capacity to do the real problem does not exist— either in time or in space. Once he reaches this situation, the casual user is likely to become an involved critic. And with good reason. The casual way in which special-purpose programming languages are often thrown into the world—perhaps to get another publication on one’s list, a publication which the specialists will not be able to read and the programmers will not want to—is a disgrace to the programming language design business. Not that the business is such a sparkling example in its own backyard—multipurpose languages—but when we move outside of the inner circle of computer professionals, we have an additional social responsibility toward those who cannot be expected to know how to protect themselves against a bad programming language. And here comes the pitch yet one more time: let’s stop churning these languages out of our foreheads like full-blown goddesses and develop them in an environment where sound behavioral principles can be used in their design and testing. Let’s become professional designers as well as pro- fessional programmers. SUMMARY On the cover of Jean Sammet’s book, Programming Languages: History and Fundamentals, there is a picture reminiscent of Brueghel’s Tower of Babel. The original painting is in Vienna, in the Kunsthistoriches Museum, but a wall-sized reproduction graces the conference room, across the ring, of the IBM Vienna Laboratory, where the PL/I Universal Language Document was born. All around the world, where programmers gather, one is likely to see some literary or pictorial reference to the Tower of Babel. Looking at the cover of Sammet’s book, the reason is not hard to find—decorating the tower are the names of 117 programming languages. Actually, there are 118, for, although it is not listed in the index of this comprehensive book, there was once a programming language named BABEL—the name which tops the tower. Indeed, to my knowledge, there are a number of languages (not even including ‘‘assembler”’ languages, which Sammet doesn’t even attempt to

242 Programming Tools cover in her list) that are not listed—and mercifully so. The 117 repre- sented languages cover a period of about fifteen years, from 1952 (Short Code) to 1967, the latest language reference given. This average of eight new languages a year (worth mentioning) is misleading, since for the first ten years of the period the average was no more than two or three a year. The average for the last five years of the period is closer to twenty languages per year, and increasing toward the end. It seems likely that Sammet’s 785 page tome will be the last work of its kind—at least the last to fit in one volume. If my extrapolations are correct, in 1972 pro- gramming languages will be invented at the rate of one a week—or more, if we consider the ones which never even make the literature, and enor- mously more if we consider dialects. Is this bloom of languages bad, like the bloom of algae in a swimming hole? Or is it a healthy outpouring of a much needed original effort? We have passed the time when a language can simply be “invented” and then cast upon the literature, or upon the broad backs of all the pro- grammers in a particular installation or working with a particular machine We must now raise our sights and ask more of the would-be language inventor—we must ask him to show some indication of psychological usefulness of his language. But the inventors are not the only ones to bear the blame—the programming language theoreticians have too long ignored the psychological side of their trade. Programming is not a branch of mathematics, it is a unique form of communication in which human beings take an active role and machines often a passive one. Perhaps our troubles with programming languages stem from the unidirectionality of the communication. Only when the theoreticians turn to the ‘“‘dialogue”’ aspects of programming “‘language’”’ will they finally be forced to recog- nize that they are not students of symbol manipulation, but of human behavior. QUESTIONS For Managers

  1. What programming languages are used in your shop? How frequently do you have occasion to look at a program? Do you see the program- ming language as an aid to your managerial task, or do you see it as a secret language in which programmers can communicate behind your back?
  2. What psychological factors do you consider when deciding to introduce a new programming language into your shop? What kinds of experi- ments do you do? |

Some Principles for Programming Language Design 243 For Programmers

  1. Using your best-known programming language, give and discuss sev- eral uses of: a. uniformity b. nonuniformity c. covert categories
  2. Of all the languages you know, which is the most compact? What makes it compact? Does its compactness depend upon the limited range of problems for which you use it?
  3. List all the ways in which your best-known programming language can yield nonlocal effects. What influence do these effect have on debug- ging? Give examples of bugs due to nonlocality, and how you even- tually found them.
  4. Take some programs written by others and classify them by the ease with which they can be understood, for a given length of program. Count the statement labels and the branch instructions or statements, and see if there is any correlation between clarity and lack of branch- ing. Also, compute the average length of a branch, and see what correlation that has with clarity.
  5. Give some examples of the way in which the programming languages you previously knew affect the way in which you now program in your most recent language. Give some examples of ways in which the programming languages you know affect your thought processes in areas other than programming.
  6. If you know a special-purpose programming language, describe the ways in which it takes advantage of: limitation of the universe of discourse consonant data structures consonant processing functions consonant terminology aoop BIBLIOGRAPHY Rubey, Raymond J., et a/., Guide to PL/I, Vol. 1: Comparative evaluation Detroit: American Data Processing, 1969 Brooks, F. P., et. al. Guide to PL/I. Vol. 2: Experiences with PL/I Detroit: Ameri- can Data Processing, 1969 These two documents represent two approaches to assessing the value of a new programming language, in this case PL/I. The second is a rather conventional seminar-discussion approach, in which participants state their opinions of the value of PL/I in its various parts and as a whole. The first, however, is one of the few attempts to study a program language behaviorally. The method was to eliminate individual variation by having the same programmer program the same problem in

244 Programming Tools two different languages—COBOL-PL/I, FORTRAN-PL/I, or JOVIAL-PL/I. The authors of the study are evidently programmers, not behavioral scientists, but they are not without sophistication. Would-be experimenters would do well to com- pare this study with Sackman’s work, perhaps leading them to something in between. Sammet, Jean E., Programming Languages: History and Fundamentals, Englewood Cliffs, N.J., Prentice-Hall, 1969. Along with its other qualities, this book is a gold mine of information on special- purpose languages, and, by induction, their design. Wirth, N., PL 360, A Programming Language for the 360 Computers, Communica- tions of the ACM, 15, No. 1 (Jan. 1968), pp. 37-74. Some people feel quite strongly that the only truly general-purpose language can be some sort of assembly language. But the difficulty of assembly language— particularly the lack of linearity—makes this a most discouraging prognostication. One bright ray of hope, however, is the approach taken by Wirth, to impose the advantages of block structure on an assembly language whiie still keeping, at least conceptually, full control of the code generated at the machine level. Cer- tainly this is one of the most important papers for the future of systems program- ming to come out in the last decade. Weinberg, G. M., PL/I Programming: A Manual of Style, New York, McGraw-Hill, 1970. For detailed discussions of principles of language design in the context of PL/I, see especially Chapter 2. Klerer, M., and J. May, A User-Oriented Programming Language, Computer Journal, 8, No. 2 (July 1965), pp. 103-109. From a language design point of view, the most intriguing feature of the Klerer and May system is the acceptance—indeed, the welcoming—of ambiguity. The second most intriguing feature is the lack of a name for the language—either the height of modesty or of egoism. For more complete references, see Sammet. Shaw, C. J., Decision Tables—An Annotated Bibliography, Santa Monica, System Development Corp. (April 1965), TM-2288/000/000 (14 pp.). Dixon, Paul, Decision Tables and Their Application, Computers and Automation, 13, (April 1964), pp. 14-19. The Decision Table Movement flourished in the early sixties, then seemed to go underground, and now is reviving. Unfortunately, the revival seems to be concen- trated on formal problems on the one hand, and ill-conceived textbooks on the other. We have yet to see any empirical data on the value of decision tables in any psychological sense, which is particuiarly unfortunate in view of the claim that they represent a more natural way of programming than procedure-oriented lan- guages. For those researchers who would like to throw some light on this murky field, we offer the bibliography and review article above, which should bring things up to date circa 1965. At the time of this writing, there really seems nothing better. Weinberg, G. M., Experiments in Problem Solving (Doctoral Thesis), Ann Arbor, University Microfilms, 1965. A study into the mental forms that subjects choose when given the freedom to choose, with implications for the types of structures most desirable in program- ming languages. Whorf, Benjamin Lee, Language, Thought, and Reality, Cambridge, Mass., M.I.T. Press, 1956.

Some Principles for Programming Language Design 245 Whorf was an anthropologist or anthropological linguist who, through his study of non-“‘Standard-American-European” languages, gained great insights into the in- fluence of language on thought—such insights as the significance of covert cate- gories. Some of these selected writings are highly technical accounts of American Indian languages, and the others are accessible to the non-linguist. They are not always easy to read because of the unfamiliarity of the modes of thought described. And who knows, maybe even the technical articles would be useful. Can we possibly be so ethnocentric as to believe that only European languages contain useful models for computer programming? Perhaps we can, but not after reading Whorf. Zipf, G. K., Human Behavior and the Principle of Least Effort, Cambridge, Mass., Addison-Wesley, 1949. Zipf, like Whorf, was outside the intellectual! fads of his time, and was a long time in gaining recognition. His work on the principles of language change should be read by anyone who has any interest, for example, in ‘‘dialects” of programming language, and particularly in “nonredundant” programming. Shannon, C. E., Prediction and Entropy in Printed English, Bell System Technical Journal, Vol. 30 (1951), pp. 50-64. See next reference for discussion. Burton, N. G., and J. C. R. Licklider, Long-Range Constraints in the Statistical Structure of Printed English, American Journal of Psychology, Vol. 68 (1955), pp. 650-653. These two articles attempt to measure the redundancy in English printed text, and they suggest an approach to measuring the redundancy of a program or a programming language. There are many difficulties in conducting such an experi- ment, such as selection of appropriate programs and texts, but our early results indicate that consistent differences exist among programming languages. Miller, George A., The Magical Number Seven, Plus-or-minus Two: Some Limits on Our Capacity for Processing Information, Psychological Review, 63, No. 2 (March 1956), pp. 81-97. In this appealing little article, Miller describes how the human mind is limited in its information capacity, more or less, and what kinds of strategies it uses to over- come these limitations, such as chunking. These strategies seem to follow the kinds of strategies programmers use. Smith, B., Memory, London, Allen and Unwin, Ltd., 1966. Yates, F. A., The Art of Memory, Chicago, The University of Chicago Press, 1966. Minsky, M., Semantic Information Processing, Cambridge, Mass., M.I.T. Press, 1968. Three very different views of memory and the sorts of mental processes toward which programming languages are designed, or should be designed. One problem, of course, with designing programming languages according to psychological principles, is that there is so much controversy over mental processes, especially the “higher” processes such as memory, which are so important (or are they?) to programming.

A Silver Anniversary Notes Comments on Chapter 12: Some Principles for Programming Language Design A few years ago, an interviewer asked, “What do you consider your greatest contribution to Computer Science?” (The capital letters were implied in the spo- ken question.) My first reaction was that | have contributed nothing to Computer Science (which | don’t regard as a “science”), but then the following answer popped out of my mouth. “My greatest contribution to computing is that | never invented a programming language.” | don’t know where that answer came from, but | realize that it was entirely a cowardly cop-out. Looking over the first edition, | see that | was already protect- ing myself from blame in the way programmers usually do—by moving to the meta-level. It’s true that | never designed a programming language, but | did design principles on which programming languages were designed—that is, | was a metadesigner of programming languages. Being a metadesigner allowed me to sit on the sidelines and comment, with- out taking responsibility. Therefore, | now own up to having been one of the commentators on the earliest assembly languages; on FORTRAN (I shared a computer with the developers, critiqued their object code, and wrongly predicted that FORTRAN would never last); on APL (I was in the group to which Ken Iverson first made his language ideas available for public criticism); on COBOL (through the MetaCOBOL effort); on PL/I; on Simula; on IBM System/360 machine language and job control language (confessions aren’t easy); on Pascal (with Niklaus Wirth in Switzerland); on the Vienna Definition Language

Silver Anniversary Notes for formal description of programming languages (with Heinz Zemanek’s team at the IBM Vienna Laboratories). | even had a few things to say (of unknown influ- ence) on the early versions of Algol(s), C, and Smalltalk, plus—and this is the part I’m most proud of—on dozens of languages that | may have helped be still- born (including at least three with the name “Magic’). On a trip to Eastern Europe in 1970, | was shocked to hear myself referred to as “the King of Programming Languages.” So much for self-awareness. In recent years, I’ve tried to repent my syns and stay out of the language-comment- ing business, but it’s been difficult. What I’ve realized is perhaps the most impor- tant principle of programming psychology: Every programmer is a metalanguage expert. | think that is the only comment | dare make on a chapter called “Some Principles for Programming Language Design.”

Powered by TurnKey Linux.