Programming: Augusta Ada Countess of Lovelace, in Notes On The Analytical Engine, 1843
Programming: Augusta Ada Countess of Lovelace, in Notes On The Analytical Engine, 1843
3
The Analytical Engine has no pretensions whatever to originate any thing. It can
do whatever we know how to order it to perform. It can follow analysis; but it
has no power of anticipating any analytical relations or truths. Its province is to
assist us in making available what we are already acquainted with.
Augusta Ada Countess of Lovelace, in Notes on the Analytical Engine, 1843
Complexity. Although English may seem simple to you now, it took many years
of intense effort (most of it subconscious) for you to learn it. Despite using it for
most of their waking hours for many years, native English speakers know a small
fraction of the entire language. The Oxford English Dictionary contains 615,000
words, of which a typical native English speaker knows about 40,000.
Ambiguity. Not only do natural languages have huge numbers of words, most
words have many different meanings. Understanding the intended meaning of
an utterance requires knowing the context, and sometimes pure guesswork.
For example, what does it mean to be paid biweekly? According to the American
Heritage Dictionary1 , biweekly has two definitions:
biweekly).
3 Carl C. Gaither and Alma E. Cavazos-Gaither, Practically Speaking: A Dictionary of Quotations
word means “more than one of the original word’s meaning”. This rule works for
most words: word 7→ words, language 7→ languages, person 7→ persons.4
It does not work for all words, however. The plural of goose is geese (and gooses
is not an English word), the plural of deer is deer (and deers is not an English
word), and the plural of beer is controversial (and may depend on whether you
speak American English or Canadian English).
These irregularities can be charming for a natural language, but they are a con-
stant source of difficulty for non-native speakers attempting to learn a language.
There is no sure way to predict when the rule can be applied, and it is necessary
to memorize each of the irregular forms.
Uneconomic. It requires a lot of space to express a complex idea in a natural lan- I have made this
guage. Many superfluous words are needed for grammatical correctness, even letter longer than
usual, only because
though they do not contribute to the desired meaning. Since natural languages I have not had the
evolved for everyday communication, they are not well suited to describing the time to make it
precise steps and decisions needed in a computer program. shorter.
Blaise Pascal, 1657
As an example, consider a procedure for finding the maximum of two numbers.
In English, we could describe it like this:
To find the maximum of two numbers, compare them. If the first num-
ber is greater than the second number, the maximum is the first number.
Otherwise, the maximum is the second number.
Perhaps shorter descriptions are possible, but any much shorter description
probably assumes the reader already knows a lot. By contrast, we can express
the same steps in the Scheme programming language in very concise way (don’t
worry if this doesn’t make sense yet—it should by the end of this chapter):
(define (bigger a b) (if (> a b) a b))
Another reason there are many different programming languages is that they
are at different levels of abstraction. Some languages provide programmers with
detailed control over machine resources, such as selecting a particular location
in memory where a value is stored. Other languages hide most of the details of
the machine operation from the programmer, allowing them to focus on higher-
level actions.
Ultimately, we want a program the computer can execute. This means at the
lowest level we need languages the computer can understand directly. At this
level, the program is just a sequence of bits encoding machine instructions.
Code at this level is not easy for humans to understand or write, but it is easy
for a processor to execute quickly. The machine code encodes instructions that
direct the processor to take simple actions like moving data from one place to
another, performing simple arithmetic, and jumping around to find the next in-
struction to execute.
For example, the bit sequence 1110101111111110 encodes an instruction in the
Intel x86 instruction set (used on most PCs) that instructs the processor to jump
backwards two locations. Since the instruction itself requires two locations of
space, jumping back two locations actually jumps back to the beginning of this
instruction. Hence, the processor gets stuck running forever without making
any progress.
Grace Hopper
Image courtesy Computer
History Museum (1952)
The computer’s processor is designed to execute very simple instructions like
jumping, adding two small numbers, or comparing two values. This means each
instruction can be executed very quickly. A typical modern processor can exe-
cute billions of instructions in a second.5
Until the early 1950s, all programming was done at the level of simple instruc-
tions. The problem with instructions at this level is that they are not easy for
humans to write and understand, and you need many simple instructions be-
fore you have a useful program.
compiler A compiler is a computer program that generates other programs. It translates
an input program written in a high-level language that is easier for humans to
create into a program in a machine-level language that can be executed by the
computer. Admiral Grace Hopper developed the first compilers in the 1950s.
interpreter An alternative to a compiler is an interpreter. An interpreter is a tool that trans-
lates between a higher-level language and a lower-level language, but where a
compiler translates an entire program at once and produces a machine language
program that can be executed directly, an interpreter interprets the program a
Nobody believed small piece at a time while it is running. This has the advantage that we do not
that I had a have to run a separate tool to compile a program before running it; we can sim-
running compiler
and nobody would
ply enter our program into the interpreter and run it right away. This makes it
touch it. They told easy to make small changes to a program and try it again, and to observe the
me computers could state of our program as it is running.
only do arithmetic.
Grace Hopper One disadvantage of using an interpreter instead of a compiler is that because
the translation is happening while the program is running, the program exe-
cutes slower than a compiled program. Another advantage of compilers over
5 A “2GHz processor” executes 2 billion cycles per second. This does not map directly to the num-
ber of instructions it can execute in a second, though, since some instructions take several cycles to
execute.
Chapter 3. Programming 39
interpreters is that since the compiler translates the entire program it can also
analyze the program for consistency and detect certain types of programming
mistakes automatically instead of encountering them when the program is run-
ning (or worse, not detecting them at all and producing unintended results).
This is especially important when writing critical programs such as flight con-
trol software — we want to detect as many problems as possible in the flight
control software before the plane is flying!
Since we are more concerned with interactive exploration than with performance
and detecting errors early, we use an interpreter instead of a compiler.
3.3 Scheme
The programming system we use for the first part of this book is depicted in
Figure 3.1. The input to our programming system is a program written in a pro-
gramming language named Scheme. A Scheme interpreter interprets a Scheme
program and executes it on the machine processor.
Scheme was developed at MIT in the 1970s by Guy Steele and Gerald Sussman,
based on the LISP programming language that was developed by John McCarthy
in the 1950s. Although many large systems have been built using Scheme, it is
not widely used in industry. It is, however, a great language for learning about
computing and programming. The primary advantage of using Scheme to learn
about computing is its simplicity and elegance. The language is simple enough
that this chapter covers nearly the entire language (we defer describing a few
aspects until Chapter 9), and by the end of this book you will know enough to
implement your own Scheme interpreter. By contrast, some programming lan-
guages that are widely used in industrial programming such as C++ and Java
require thousands of pages to describe, and even the world’s experts in those
languages do not agree on exactly what all programs mean.
Although almost everything we describe should work in all Scheme interpreters,
for the examples in this book we assume the DrRacket programming environ-
ment which is freely available from http://racket-lang.org/. DrRacket includes
(define (bigger a b)
(if (> a b) a b))
Scheme Program
(bigger 3 4)
Interpreter
(DrRacket)
Processor
interpreters for many different languages, so you must select the desired lan-
guage using the Language menu. The selected language defines the grammar
and evaluation rules that will be used to interpret your program. For all the ex-
amples in this book, we use a version of the Scheme language named Pretty Big.
3.4 Expressions
A Scheme program is composed of expressions and definitions (we cover defi-
expression nitions in Section 3.5). An expression is a syntactic element that has a value.
The act of determining the value associated with an expression is called evalua-
evaluation tion. A Scheme interpreter, such as the one provided in DrRacket, is a machine
for evaluating Scheme expressions. If you enter an expression into a Scheme
interpreter, the interpreter evaluates the expression and displays its value.
Expressions may be primitives. Scheme also provides means of combination
for producing complex expressions from simple expressions. The next subsec-
tions describe primitive expressions and application expressions. Section 3.6
describes expressions for making procedures and Section 3.7 describes expres-
sions that can be used to make decisions.
3.4.1 Primitives
An expression can be replaced with a primitive:
Expression ::⇒ PrimitiveExpression
As with natural languages, primitives are the smallest units of meaning. Hence,
the value of a primitive is its pre-defined meaning.
Scheme provides many different primitives. Three useful types of primitives are
described next: numbers, Booleans, and primitive procedures.
Numbers. Numbers represent numerical values. Scheme provides all the kinds
of numbers you are familiar with including whole numbers, negative numbers,
decimals, and rational numbers.
Example numbers include:
150 0 −12
3.14159 3/4 999999999999999999999
Numbers evaluate to their value. For example, the value of the primitive expres-
sion 1120 is 1120.
Booleans. Booleans represent truth values. There are two primitives for repre-
senting true and false:
PrimitiveExpression ::⇒ true | false
The meaning of true is true, and the meaning of false is false. In the DrRacket
interpreter, #t and #f are used to represent the primitive truth values. So, the
value true appears as #t in the interactions window.
Primitive Procedures. Scheme provides primitive procedures corresponding to
function many common functions. Mathematically, a function is a mapping from inputs
Chapter 3. Programming 41
to outputs. For each valid input to the function, there is exactly one associated
output. For example, + is a procedure that takes zero or more inputs, each of
which must be a number. Its output is the sum of the values of the inputs. Table
3.1 describes some primitive procedures for performing arithmetic and com-
parisons on numbers.
using the grammar rules. The same process will allow us to understand how any
expression is evaluated.
The grammar rule for application is:
Expression
ApplicationExpression
( Expression MoreExpressions )
1 PrimitiveExpression e
Expression
ApplicationExpression
( Expression MoreExpressions )
(∗ 10 10) ApplicationExpression e
(+ 25 25)
This tree is similar to the previous tree, except instead of the subexpressions
of the first application expression being simple primitive expressions, they are
now application expressions. (Instead of showing the complete parse tree for
the nested application expressions, we use triangles.)
To evaluate the output application, we need to evaluate all the subexpressions.
The first subexpression, +, evaluates to the primitive procedure. The second
subexpression, (∗ 10 10), evaluates to 100, and the third expression, (+ 25 25),
evaluates to 50. Now, we can evaluate the original expression using the values
for its three component subexpressions: (+ 100 50) evaluates to 150.
Exercise 3.1. Draw a parse tree for the Scheme expression (+ 100 (∗ 5 (+ 5 5)))
and show how it is evaluated.
Exercise 3.2. Predict how each of the following Scheme expressions is evalu-
ated. After making your prediction, try evaluating the expression in DrRacket. If
the result is different from your prediction, explain why the Scheme interpreter
evaluates the expression as it does.
a. 1120
b. (+ 1120)
c. (+ (+ 10 20) (∗ 2 0))
d. (= (+ 10 20) (∗ 15 (+ 5 5)))
e. +
f. (+ + <)
Exercise 3.3. For each question, construct a Scheme expression and evaluate it
in DrRacket.
a. How many seconds are there in a year?
b. For how many seconds have you been alive?
c. For what fraction of your life have you been in school?
44 3.5. Definitions
3.5 Definitions
Scheme provides a simple, yet powerful, mechanism for abstraction. A defini-
tion introduces a new name and gives it a value:
Definition ::⇒ (define Name Expression)
After a definition, the N ame in the definition is now associated with the value of
the expression in the definition. A definition is not an expression since it does
not evaluate to a value.
A name can be any sequence of letters, digits, and special characters (such as
−, >, ?, and !) that starts with a letter or special character. Examples of valid
names include a, Ada, Augusta-Ada, gold49, !yuck, and yikes!\%@\#. We don’t
recommend using some of these names in your programs, however! A good pro-
grammer will pick names that are easy to read, pronounce, and remember, and
that are not easily confused with other names.
After a name has been bound to a value by a definition, that name may be used
in an expression:
Expression ::⇒ NameExpression
NameExpression ::⇒ Name
The value of a NameExpression is the value associated with the Name. (Alert
readers should be worried that we need a more precise definition of the meaning
of definitions to know what it means for a value to be associated with a name.
This informal notion will serve us well for now, but we will need a more precise
explanation of the meaning of a definition in Chapter 9.)
Below we define speed-of-light to be the speed of light in meters per second,
define seconds-per-hour to be the number of seconds in an hour, and use them
to calculate the speed of light in kilometers per hour:
> (define speed-of-light 299792458)
> speed-of-light
299792458
> (define seconds-per-hour (∗ 60 60))
> (/ (∗ speed-of-light seconds-per-hour) 1000)
1079252848 4/5
Chapter 3. Programming 45
3.6 Procedures
In Chapter 1 we defined a procedure as a description of a process. Scheme pro-
vides a way to define procedures that take inputs, carry out a sequence of ac-
tions, and produce an output. Section 3.4.1 introduced some of Scheme’s prim-
itive procedures. To construct complex programs, however, we need to be able
to create our own procedures.
Procedures are similar to mathematical functions in that they provide a map-
ping between inputs and outputs, but they differ from mathematical functions
in two important ways:
State. In addition to producing an output, a procedure may access and mod-
ify state. This means that even when the same procedure is applied to the
same inputs, the output produced may vary. Because mathematical func-
tions do not have external state, when the same function is applied to the
same inputs it always produces the same result. State makes procedures
much harder to reason about. We will ignore this issue until Chapter 9, and
focus until then only on procedures that do not involve any state.
Resources. Unlike an ideal mathematical function, which provides an instan-
taneous and free mapping between inputs and outputs, a procedure re-
quires resources to execute before the output is produced. The most impor-
tant resources are space (memory) and time. A procedure may need space
to keep track of intermediate results while it is executing. Each step of a
procedure requires some time to execute. Predicting how long a procedure
will take to execute and finding the fastest procedure possible for solving
some problem are core problems in computer science. We consider this
throughout this book, and in particular in Chapter 7.
For the rest of this chapter, we view procedures as idealized mathematical func-
tions: we consider only procedures that involve no state and do not worry about
the resources required to execute our procedures.
as its output.
(lambda (a b) (+ a b))
Procedure that takes two inputs, and produces the sum of the input values
as its output.
(lambda () 0)
Procedure that takes no inputs, and produces 0 as its output. The result of
applying this procedure to any argument is always 0.
(lambda (a) (lambda (b) (+ a b)))
Procedure that takes one input (a), and produces as its output a procedure
that takes one input and produces the sum of a and that input as its output.
higher-order This is an example of a higher-order procedure. Higher-order procedures
procedure produce procedures as their output or take procedures as their arguments.
This can be confusing, but is also very powerful.
> (square 2)
4
> (square 1/4)
1/16
> (square (square 2))
16
need the begin expression until we start dealing with procedures that have side effects. We describe
the begin special form in Chapter 9.
48 3.7. Decisions
This incorporates the lambda invisibly into the definition, but means exactly
the same thing. For example,
(define square (lambda (x) (∗ x x)))
can be written equivalently as:
(define (square x) (∗ x x))
Exercise 3.5. Define a procedure, cube, that takes one number as input and
produces as output the cube of that number.
Exercise 3.6. Define a procedure, compute-cost, that takes as input two num-
bers, the first represents that price of an item, and the second represents the
sales tax rate. The output should be the total cost, which is computed as the
price of the item plus the sales tax on the item, which is its price times the sales
tax rate. For example, (compute-cost 13 0.05) should evaluate to 13.65.
3.7 Decisions
To make more useful procedures, we need the actions taken to depend on the
input values. For example, we may want a procedure that takes two numbers as
inputs and evaluates to the greater of the two inputs. To define such a procedure
we need a way of making a decision. The IfExpression expression provides a way
of using the result of one expression to select which of two possible expressions
to evaluate:
Expression ::⇒ IfExpression
IfExpression ::⇒ (if ExpressionPredicate
ExpressionConsequent
ExpressionAlternate )
The IfExpression replacement has three Expression terms. For clarity, we give
each of them names as denoted by the Predicate, Consequent, and Alternate
subscripts. To evaluate an IfExpression, first evaluate the predicate expression,
ExpressionPredicate . If it evaluates to any non-false value, the value of the IfEx-
pression is the value of ExpressionConsequent , the consequent expression, and the
alternate expression is not evaluated at all. If the predicate expression evaluates
to false, the value of the IfExpression is the value of ExpressionAlternate , the alter-
nate expression, and the consequent expression is not evaluated at all.
The predicate expression determines which of the two following expressions is
evaluated to produce the value of the IfExpression. If the value of the predicate
is anything other than false, the consequent expression is used. For example, if
the predicate evaluates to true, to a number, or to a procedure the consequent
expression is evaluated.
special form The if expression is a special form. This means that although it looks syntacti-
cally identical to an application (that is, it could be an application of a procedure
named if), it is not evaluated as a normal application would be. Instead, we have
Chapter 3. Programming 49
a special evaluation rule for if expressions. The reason a special evaluation rule
is needed is because we do not want all the subexpressions to be evaluated. With
the normal application rule, all the subexpressions are evaluated first, and then
the procedure resulting from the first subexpression is applied to the values re-
sulting from the others. With the if special form evaluation rule, the predicate
expression is always evaluated first and only one of the following subexpressions
is evaluated depending on the result of evaluating the predicate expression.
This means an if expression can evaluate to a value even if evaluating one of its
subexpressions would produce an error. For example,
(if (> 3 4) (∗ + +) 7)
evaluates to 7 even though evaluating the subexpression (∗ + +) would produce
an error. Because of the special evaluation rule for if expressions, the conse-
quent expression is never evaluated.
Now that we have procedures, decisions, and definitions, we can understand the
bigger procedure from the beginning of the chapter. The definition,
(define (bigger a b) (if (> a b) a b))
is a condensed procedure definition. It is equivalent to:
(define bigger (lambda (a b) (if (> a b) a b)))
This defines the name bigger as the value of evaluating the procedure expression
(lambda (a b) (if (> a b) a b)). This is a procedure that takes two inputs, named
a and b. Its body is an if expression with predicate expression (> a b). The
predicate expression compares the value that is bound to the first parameter, a,
with the value that is bound to the second parameter, b, and evaluates to true if
the value of the first parameter is greater, and false otherwise. According to the
evaluation rule for an if expression, when the predicate evaluates to any non-
false value (in this case, true), the value of the if expression is the value of the
consequent expression, a. When the predicate evaluates to false, the value of
the if expression is the value of the alternate expression, b. Hence, our bigger
procedure takes two numbers as inputs and produces as output the greater of
the two inputs.
Exercise 3.7. Follow the evaluation rules to evaluate the Scheme expression:
(bigger 3 4)
where bigger is the procedure defined above. (It is very tedious to follow all of
the steps (that’s why we normally rely on computers to do it!), but worth doing
once to make sure you understand the evaluation rules.)
50 3.8. Evaluation Rules
Exercise 3.8. Define a procedure, xor, that implements the logical exclusive-or
operation. The xor function takes two inputs, and outputs true if exactly one of
those outputs has a true value. Otherwise, it outputs false. For example, (xor true
true) should evaluate to false and (xor (< 3 5) (= 8 8)) should evaluate to true.
Exercise 3.9. Define a procedure, absvalue, that takes a number as input and
produces the absolute value of that number as its output. For example, (ab-
svalue 3) should evaluate to 3 and (absvalue −150) should evaluate to 150.
Exercise 3.10. Define a procedure, bigger-magnitude, that takes two inputs, and
outputs the value of the input with the greater magnitude (that is, absolute dis-
tance from zero). For example, (bigger-magnitude 5 −7) should evaluate to −7,
and (bigger-magnitude 9 −3) should evaluate to 9.
Exercise 3.11. Define a procedure, biggest, that takes three inputs, and produces
as output the maximum value of the three inputs. For example, (biggest 5 7 3)
should evaluate to 7. Find at least two different ways to define biggest, one using
bigger, and one without using it.
Abbreviation for
(define Name (lambda Parameters) Expression)
Expression ::⇒ PrimitiveExpression | NameExpression
| ApplicationExpression
| ProcedureExpression | IfExpression
The value of the expression is the value of the replacement
expression.
PrimitiveExpression ::⇒ Number | true | false | primitive procedure
The evaluation rule for an application (Rule 3b) uses apply to perform the ap-
plication. Apply is defined by the two application rules:
Application Rule 1: Primitives.
To apply a primitive procedure, just do it.
Application Rule 2: Constructed Procedures.
To apply a constructed procedure, evaluate the body of the procedure with
each parameter name bound to the corresponding input expression value.
Application Rule 2 uses the evaluation rules to evaluate the expression. Thus,
the evaluation rules are defined using the application rules, which are defined
using the evaluation rules! This appears to be a circular definition, but as with
the grammar examples, it has a base case. Some expressions evaluate without
using the application rules (e.g., primitive expressions, name expressions), and
some applications can be performed without using the evaluation rules (when
the procedure to apply is a primitive). Hence, the process of evaluating an ex-
pression will sometimes finish and when it does we end with the value of the
expression.7
7 This does not guarantee that evaluation always finishes, however! The next chapter includes
3.9 Summary
At this point, we have covered enough of Scheme to write useful programs (even
if the programs we have seen so far seem rather dull). In fact (as we show in
Chapter 12), we have covered enough to express every possible computation!
We just need to combine these constructs in more complex ways to perform
more interesting computations. The next chapter (and much of the rest of this
book), focuses on ways to combine the constructs for making procedures, mak-
ing decisions, and applying procedures in more powerful ways.