ROS: I mean, what exactly do you do?
PLAYER: We keep to our usual stuff, more or less, only inside out. We do on stage things that are supposed to happen off. Which is a kind of integrity, if you look on every exit as being an entrance somewhere else
Tom Stoppard Rosencrantz and Guildenstern are Dead
We have reached the end of this introduction to computing and program design. While there is more to learn about both subjects, this is a good point to stop, to summarize, and to look ahead.
In elementary school, you learned to calculate with numbers. At first you used numbers to count real things: three apples, five friends, twelve bagels. A bit later you encountered addition, subtraction, multiplication, and even division. Middle school introduced fractions, but it was still all about numbers. In high school, latest, you found out about variables and functions. Once again, variables were for numbers and functions related numbers to numbers. They called it algebra.
Because you used numbers all the way from first grade through senior year in high school, you didn’t think much of numbers as a means to represent information about the real world. Yes, you had started with three bears, five wolves, and twelve horses, but by high school nobody reminded you of this relationship.
When you move from calculating to computing, the mapping from information to data and back becomes central. Nowadays computer programs process representations of music, videos, molecules, chemical compounds, business case studies, electrical diagrams, and blue prints. Fortunately, you don’t need to encode all this information with numbers (or worse just two numbers: 0 and 1) when you program; life would be unimaginable tedious otherwise. Instead, computing generalizes arithmetic and algebra so that your programs can compute with strings, booleans, characters, structures and even functions of your choice.
Like numbers, these forms of data come with basic operations. To compute means to apply these functions to data. The computation obeys laws with equational laws explaining how these operations process data.
Computing also means running functions, which like in mathematics, combine basic operations and other functions. There are two fundamental combination mechanisms: function composition and conditional expressions. The former means that the result of one function becomes the argument of another one. The latter represents a choice among several possibilities. Each of these combination mechanisms comes with a law that governs how computations proceed when they encounter a function combination.
Programs consist of many functions, sometimes thousands and tens of thousands, and to run a program means to apply one of these functions. Using the laws of data and function combination, any programmer can, in principle, understand how any program processes its inputs and produces its outputs. But people are too slow at this task when it involves huge volumes of data and large numbers of functions and operations. Instead they leave the actual computing to computers, which are extremely fast and good at using these generalized laws of arithmetic and algebra.
Programmers design programs, meaning data representations and functions. Some of these functions are plain structural traversals, a few use generative recursion. Many of them are compositions of functions. While programmers occasionally compose their own functions, most often they use other programmer’s functions.
A typical programming project requires the collaboration of many programmers. Each programmer contributes one or more components to the system, which from the perspective of this book, means a collection of functions. Since the life span of software systems also comprises many years, it is common that some programmers leave and others join project teams during this period.
In such a dynamic context, programmers cannot hope to produce high quality software without a strong discipline. The key is to understand the design of programs as a means of communication among programmers; the goal is to describe computations so that others can easily read and comprehend the code. For that reason, the design of every program and every piece of a program must rely on a method that produces code in a systematic manner. Thus when others approach this code, its very shape and organization conveys the underlying ideas.
The design recipe of this book is one of these methods. It starts with an analysis of the world of information and a description of the classes of data that represent the relevant information. Then you make a plan, a work list of functions needed. Iterative refinement dictates that you start with a subset of functions that quickly yields a (partially) working product. A client can interact with this product and make suggestions and wishes.
Designing a program, a component, or even just a function requires a rigorous understanding of what it computes. Unless you can describe the purpose of a piece of code with a concise statement, you cannot produce anything useful for future programmers. It always helps to make up and work through examples. To confirm that the program works at least on your examples, you turn these examples into a suite of tests. This test suite is even more important when it comes to future modifications of the program. Anyone who changes the code can re-run these tests and reconfirm that the program still works for the basic examples.
Eventually you will write and distribute real-world programs, meaning other
programmers or perhaps real-world users get error messages from your code
or find differences between expected behavior and actual behavior. In this
situation, your immediate task is to formulate a test case for which your
code fails. Then you work through this failure, modify the program, and
re-run the complete test suite—
No matter how hard you work, a function or program isn’t done the first time it passes the test suite. You must find time to inspect it for flaws, for violations of design concepts, for repetitions. If you find any patterns, form new abstractions or use existing abstractions to eliminate these patterns.
If you respect these guidelines, you will produce solid software. It will work because you understand why and how it works. Others who must modify or enhance your software will understand it, because its shape communicates its development process. Still, to produce great software, you must practice, practice, practice. And you will have to learn a lot more about program design and computing than a first book can teach.
This book uses a series of small teaching languages to introduce programming, not a full-featured programming language. Most importantly, teaching languages protect novice programmers from the incomprehensible error messages that come with real-world languages. At the same time, the careful selection of minimal features ensures that you can easily adapt the program design recipe to other languages.
As a student of program design, your next task is to learn how the design recipe applies in the setting of a full-fledged programming language.Given your knowledge, it is easy for you to learn Racket, the language behind the teaching languages in this book. See Realm of Racket for one possible introduction. Such a language typically offers means for spelling out data definitions (classes and objects) and for formulating signatures so that they are cross-checked before the program is run (types). In addition, you will also have to learn how to scale the design recipe to the use and production of so-called frameworks and components. Roughly speaking, frameworks abstract pieces of functionality that are common to many software systems, for example, graphical user interfaces, database connections, web connectivity and so on. You need to learn to instantiate these abstractions, and your programs will compose these instances to create coherent systems. Conversely, the creation of systems also calls for their organization into components that bundle pieces of functionality. Learning to create such components, is inherently a part of scaling up your skills.
As a student of computing, you will also have to expand your understanding of computing. This book has focused on the laws that describe computing processes. In order to function as a real software engineer, you need to learn what computations costs, both at a theoretical level and a practical one. The concept of big-O is a first step in this direction; being able to measure a program’s performance and to allocate time consumption to its pieces is another one. Above and beyond these basic ideas, you will need knowledge about hardware, networking, layering of software, and specialized algorithms in various disciplines.
Some of you wanted to see what computer science was about, and you may
never have seen a future in computing for yourself. You found out how
computing is a natural outgrowth of school mathematics and that programming
is all about systematic problem solving. Now, whether you become an
accountant, a doctor, or a lab technician, you will have to solve problems
and the design recipe will help you. You will see that the recipe’s process
dimension can serve as a guide in many situations and that
remember the design recipe, wherever you go.