Understanding Programming Languages

Preface


The importance of programming languages

To say that a good programmer can write good software in any language is like saying that a good pilot can fly any aircraft: true, but irrelevant. A passenger aircraft is designed for safety, comfort and economic viability; a military aircraft is designed for performance and mission capability; an ultralite aircraft is designed for low cost and operational simplicity.

The role of language in programming has been downgraded in favor of software methodology and tools; not just downgraded, but totally repudiated when it is claimed that a well-designed system can be implemented equally well in any language. But programming languages are not just a tool; they furnish the raw material of software, the thing we look at on our screens most of the day. I believe that the programming language is one of the most important, not one of the leastimportant, factors that influence the ultimate quality of a software system. Unfortunately, too many programmers have poor linguistic skills. He/she is passionately in love with his/her ``native'' programming language, but is not able to analyze and compare language constructs, nor to understand the advantages and disadvantages of modern languages and language concepts. Too often, one hears statements that demonstrate conceptual confusion: ``Language L1 is more powerful (or more efficient) than language L2''.

This lack of knowledge is a contributing factor to two serious problems in software. The first is the ultra-conservatism that exists in the choice of programming languages. Despite the explosive advances in computer hardware and the sophistication of modern software systems, most programming is still done in languages that were developed about 1970, if not earlier. Extensive research in programming languages is never tested in practice, and software developers are forced to use tools and methodologies to compensate for obsolete language technology. It is as if airlines would refuse to try jet aircraft on the grounds that an old-fashioned propeller aircraft is perfectly capable of getting you from here to there.

The second problem is that language constructs are used indiscriminately, with little or no regard for safety or efficiency. This leads to unreliable software that cannot be maintained, as well as to inefficiencies that are solved by assembly language coding, rather than by refinement of the algorithms and the programming paradigms.

Programming languages exist only for the purpose of bridging the gap in the level of abstraction between the hardware and the real world. There is an inevitable tension between higher levels of abstraction that are easier to understand and safer to use, and lower levels of abstraction that are more flexible and can often be implemented more efficiently. To design or choose a programming language is to select an appropriate level of abstraction, and it is not surprising that different programmers prefer different levels, or that one language may be appropriate for one project and not for another. Within a specific language, a programmer should understand in depth the safety and efficiency implications of each construct in the language.

The aim of the book

The aim of this book is to help the student understand programming languages by analyzing and contrasting language constructs:

What alternatives are available to the language designer?
How are language constructs implemented?
How should they be used?


We have not hesitated to be prescriptive: to claim that accumulated experience shows that certain constructs are to be preferred, and others to be avoided or at least used with caution.

Of course, any book on programming languages should not be taken as a reference manual for any particular language. The goal is to learn to analyze languages and not to study the peculiarities of any language in depth. Nor is the book a guide to the choice of a language for any particular project. The goal is to supply the student with the conceptual tools needed to make such a decision.

Selection of the material

The author of a text on programming languages must necessarily offend at least 3975 of the 4000 or so inventors of programming languages! I made the conscious decision to focus on a very small number of languages (even if it means offending 3994 people), because I believe that I can explain most language concepts using these languages. Other languages are mentioned or surveyed only if they demonstrate some concept that is not found in the languages chosen for the mainstream of the presentation.

Much of the book is concerned with ``ordinary'' imperative languages and two languages have been chosen from this class. Representing languages with a low level of abstraction is C, which has overtaken Fortran as the dominant language in this category. To represent a higher level of abstraction we have chosen Ada which offers a much cleaner definition than the more widely known Pascal.

An additional justification for these choices is that both languages have extensions (C++ and Ada 95) that we can use to study language support for object-oriented programming, currently the dominant programming method.

Unfortunately (I believe) most programming today is still done in imperative languages, but in recent years the quality of implementations for non-imperative languages has improved so that they can be used to develop ``real'' software. The final chapters introduce functional (ML) and logic programming (Prolog) languages in the hope of convincing the student that imperative languages are not conceptual necessities for programming.

The theory of programming language syntax and semantics is beyond the scope of this text. These important subjects are best left to more advanced courses.

To prevent confusion when comparing examples from different languages, an indication like [C++] is attached to each example. In a section that discusses constructs in a specific language, no indication will be given.

Overview of the material

Part I is descriptive, defining and surveying programming languages and environments. Part II explains in great detail the basic constructs of programming languages: types, statements and subprograms. Part III continues the discussion of more advanced programming constructs such as: real numbers, static polymorphism, error handling and concurrency. Part IV is devoted to programming large systems with emphasis on language support for object-oriented programming. Finally, Part V surveys the basic concepts of functional and logic programming.

Teaching recommendations

The prerequisite for this book is at least one year of programming in some language such as Pascal or C. In any case, the student should have a basic reading knowledge of C. A familiarity with the structure and machine code of some computer will also be helpful.

There is too much material for a single course. Parts I and II together with portions of Part IV on modules and object-oriented programming can form the basis of a one-semester course for second-year undergraduates. For advanced undergraduates, the first half can be quickly reviewed in order to concentrate on the more difficult material in Parts III and IV. An advanced course should certainly include Part V supplemented by more material on some non-imperative language chosen by the instructor. Starred sections are more appropriate for advanced students.

It is possible to obtain free compilers for most languages as discussed in Appendix A. Students should also be taught how to examine the assembly language instructions that are emitted by the compilers that they use.

Exercises: since this is a book on programming languages and not on programming, the emphasis in the exercises is not on programming projects. Instead, we ask the students to dig into language descriptions, compare languages and verify compiler implementations of language constructs. The instructor is encouraged to tailor the exercises and append others, according to personal taste and the availability of tools.

The book will also be useful to programmers who wish to deepen their knowledge of the tools they use daily: programming languages.

A personal note

I admit that I prefer higher to lower levels of abstraction. This is not a prejudice, but a ``post-judice''. We software engineers have an exceedingly dismal record when it comes to producing reliable software systems, and I believe that part of the solution lies in higher levels of abstraction in programming languages. To expand on Dijkstra's saying: if you have a 100,000-line program that you can't understand, you should rewrite it as a 10,000-line program in a higher-level programming language.

My formative experience was in the early 1970's as a member of a large team of programmers working on a financial transaction system. We actually installed a new on-line system, even though we knew the system had a bug that we could not locate. Several weeks later the bug was finally trapped: it turned out that poor design of the programming language being used had turned a trivial typographical mistake into a type mismatch. A couple of years later when I first saw Pascal, I was hooked. The addiction deepened each time I helped a scientist who had wasted weeks of his/her life searching for a bug that would not have compiled successfully in Pascal. While type mismatch is certainly not the only source of programming error, it is so common and so dangerous, yet so simple to catch, that I would no more abandon strong type checking than I would drive without a seat-belt: it may be uncomfortable but even the best drivers can be involved in an accident and the discomfort is trivial relative to the potential damage.

I do not wish to get involved in language ``wars'' claiming that one language is better than another for any specific machine or application. I have attempted to analyze language constructs as objectively as I can in the hope that this will contribute to making discussions about language more scientific.

Acknowledgements

I would like to thank Kevlin A.P. Henney and David W. Barron for comprehensive reviews of the manuscript, as well as Harry Mairson, Tamar Benaya and Bruria Haberman who read portions of the manuscript. I am indebted to Amiram Yehudai for serving as my object-oriented guru: guiding me during numerous discussions and carefully checking the relevant chapters. Edmond Schonberg, Robert Dewar and their team at NYU quickly responded to my questions on GNAT, making it possible for me to learn and to write about Ada 95 even before a full compiler was available. Ian Joyner kindly provided his unpublished analysis of C++ which was most helpful. Like my previous books, this book would probably not have been written without Leslie Lamport's LaTeX!

It has been my privilege to work with a very professional and efficient publishing team at John Wiley, and I would like to thank them, especially my editor Gaynor Redvers-Mutton, for this opportunity.

Table of Contents


Chapter 1 What Are Programming Languages? 3
1.1 The wrong question 3
1.2 Imperative languages 5
1.3 Data-oriented languages 10
1.4 Object-oriented languages 14
1.5 Non-imperative languages 15
1.6 Standardization 16
1.7 Computer architecture 17
1.8 * Computability 20
1.9 Exercises 21
Chapter 2 Elements of Programming Languages 23
2.1 Syntax 23
2.2 * Semantics 25
2.3 Data 27
2.4 The assignment statement 28
2.5 Type checking 29
2.6 Control statements 30
2.7 Subprograms 31
2.8 Modules 31
2.9 Exercises 33
Chapter 3 Programming Environments 35
3.1 Editor 36
3.2 Compiler 36
3.3 Librarian 39
3.4 Linker 40
3.5 Loader 41
3.6 Debugger 41
3.7 Profiler 42
3.8 Testing tools 43
3.9 Configuration tools 43
3.10 Interpreters 44
3.11 Exercises 45
Chapter 4 Elementary Data Types 49
4.1 Integer types 49
4.2 Enumeration types 54
4.3 Character type 57
4.4 Boolean type 59
4.5 * Subtypes 60
4.6 * Derived types 61
4.7 Expressions 63
4.8 Assignment statements 67
4.9 Exercises 69
Chapter 5 Composite Data Types 71
5.1 Records 71
5.2 Arrays 74
5.3 Arrays and type checking 77
5.4 * Array subtypes in Ada 80
5.5 String type 81
5.6 Multi-dimensional arrays 83
5.7 Array implementation 84
5.8 * Representation specification 88
5.9 Exercises 93
Chapter 6 Control Structures 95
6.1 switch -/ case -statements 95
6.2 if -statements 100
6.3 Loop statements 105
6.4 for -statements 108
6.5 Sentinels 114
6.6 * Invariants 115
6.7 goto -statements 117
6.8 Exercises 119
Chapter 7 Subprograms 121
7.1 Subprograms: procedures and functions 121
7.2 Parameters 123
7.3 Passing parameters to a subprogram 125
7.4 Block structure 135
7.5 Recursion 141
7.6 Stack architecture 143
7.7 More on stack architecture 150
7.8 * Implementation on the 8086 153
7.9 Exercises 155
Chapter 8 Pointers 159
8.1 Pointer types 159
8.2 Data structures 166
8.3 Memory allocation 172
8.4 Algorithms for heap allocation 176
8.5 Exercises 179
Chapter 9 Real Numbers 181
9.1 Representations of real numbers 181
9.2 Language support for real numbers 185
9.3 The three deadly sins 189
9.4 * Real types in Ada 191
9.5 Exercises 192
Chapter 10 Polymorphism 195
10.1 Type conversion 195
10.2 Overloading 197
10.3 Generics 198
10.4 Variant records 202
10.5 Dynamic dispatching 206
10.6 Exercises 206
Chapter 11 Exceptions 209
11.1 Exception handling requirements 209
11.2 Exceptions in PL/I 211
11.3 Exceptions in Ada 211
11.4 Exceptions in C++ 214
11.5 Error handling in Eiffel 215
11.6 Exercises 219
Chapter 12 Concurrency 221
12.1 What is concurrency? 221
12.2 Shared memory 222
12.3 The mutual exclusion problem 224
12.4 Monitors and protected objects 226
12.5 Message passing 228
12.6 occam 228
12.7 Ada rendezvous 229
12.8 Linda 232
12.9 Exercises 233
Chapter 13 Program Decomposition 239
13.1 Separate compilation 240
13.2 Why are modules needed? 245
13.3 Packages in Ada 246
13.4 Abstract data types in Ada 251
13.5 How to write modules in C++ 257
13.6 Exercises 260
Chapter 14 Object-Oriented Programming 263
14.1 Object-oriented design 263
14.2 Object-oriented programming in C++ 266
14.3 Inheritance 268
14.4 Dynamic polymorphism in C++ 270
14.5 Object-oriented programming in Ada 95 277
14.6 Exercises 283
Chapter 15 More on Object-Oriented Programming 285
15.1 Structuring classes 286
15.2 Access to private components 293
15.3 Class data 298
15.4 The Eiffel programming language 302
15.5 Design considerations 309
15.6 Methods of dynamic polymorphism 313
15.7 Exercises 313
Chapter 16 Functional Programming 317
16.1 Why functional programming? 317
16.2 Functions 318
16.3 Compound types 320
16.4 Higher-order functions 322
16.5 Lazy and eager evaluation 325
16.6 Exceptions 327
16.7 Environments 328
16.8 Exercises 329
Chapter 17 Logic Programming 331
17.1 Pure logic programming 332
17.2 Unification 335
17.3 Prolog 337
17.4 Advanced logic programming concepts 345
17.5 Exercises 347
Chapter 18 Java 349
18.1 The Java model 349
18.2 The Java Language 352
18.3 Reference semantics 352
18.4 Polymorphic data structures 356
18.5 Encapsulation 357
18.6 Concurrency 360
18.7 The Java Libraries 362
18.8 Exercises 363
Appendix A Where to Get Compilers 365
Appendix B Selected Bibliography 367