ch3 Programming
ch3 Programming
Programming
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.
3-1
3-2 CHAPTER 3. PROGRAMMING
Natural languages, such as English, work reasonably well for human-human com-
munication, but are not well-suited for human-computer or computer-computer
communication. There are many reasons for this including:
Complexity. Although English may seem simple to you now (at least compared to
learning a new language!), it took many years of intense learning for you to learn
it. Despite all those years of effort, you only 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. To understand which meaning is intended
requires knowing the context, and sometimes pure guesswork. For example, what
does it mean to be paid biweekly? According to the American Heritage Dictionary
(Fourth Edition), biweekly has two definitions:
1. Happening every two weeks.
2. Happening twice a week; semiweekly.
So, depending on which definition is intended, someone who is paid biweekly
could either be paid once or four times every two weeks!
Even if we can agree on the definition of every word, the meaning of English
sentences is often ambiguous. Here is one of my favorite examples, taken from
the instructions with a shipment of ballistic missiles from the British Admiralty:2
1
Which, presumably, anyone who has gotten this far has done, at least in English!
2
Reported in The Hummus Report (http://www.textfiles.com/magazines/HUMUS/humus.005).
3.1. PROBLEMS WITH NATURAL LANGUAGES 3-3
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 proba-
bly assumes the reader knows a lot already. By contrast, we can express the same
steps in Scheme in very concise way5 :
Hence, natural languages are not well suited to programming computers. In-
stead, we need languages that are simpler, less ambiguous, more regular, more
economic, and that provide more powerful means of abstraction than natural lan-
guages. A programming language is a language that is designed to be read and
written by humans to create programs that can be executed by computers6 .
Programming languages come in many flavors. One reason for this is that they
are at different levels of abstraction. 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 zeros and
ones (e.g., 1110101111111110. . .). 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 instruction to execute. For example, the sequence
of zeros and ones encodes an instruction for the Intel x86 processor (used on
most PCs) that tells the processor to jump backwards two locations. In fact, two
locations is the amount of space needed to hold this instruction, so jumping back
two locations actually jumps back to the beginning of this instruction (hence, it
gets stuck running forever without making any progress).
The computer’s processor is designed to execute very simple instructions like this
one. This means each instruction can be executed very quickly. A typical modern
Nobody believed that I processor can execute billions of instructions in a single second.7
had a running compiler
6
and nobody would We will provide a more precise definition of programming language in Chapter ??, after we
touch it. They told me have a formal model of a computer.
7
computers could only When a computer is marketed as a “2GHz processor” that means the processor executes 2
do arithmetic. billion cycles per second. This does not map directly to the number of instructions it can execute
Grace Hopper in a second, though, since some instructions take several cycles to execute.
3.3. SCHEME 3-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 before
you have a useful program.
In the early 1950s, Admiral Grace Hopper developed the first compilers. A com-
piler is a computer program that generates other programs. It can translate an
input program written in a high-level language that is easier for humans to cre-
ate into a program in a machine-level language that is easier for a computer to
execute.
An alternative to a compiler is an interpreter. An interpreter is a tool that translates
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 small piece
at a time while it is running. This has the advantage that we do not have to run
a separate tool to compile a program before running it; we can simply enter our
program into the interpreter and run it right away. This makes it easy to make
small changes to a program and try it again, and to observe the state of our program
as it is running.
A disadvantage of using an interpreter instead of a compiler is that because the
translation is happening while the program is running, the program may execute
much slower than a similar compiled program would. Another advantage of com-
pilers over interpreters is that since the compiler translates the entire program it
can also analyze the program for consistency and detect certain types of program-
ming mistakes automatically instead of encountering them when the program is
running (or worse, not detecting them at all and producing unintended results).
This is especially important when writing large, critical programs such as flight
control software — we want to detect as many problems as possible in the flight
control software before the plane is flying!
3.3 Scheme
For now, we are more concerned with interactive exploration than with perfor-
mance and detecting errors early, so we use an interpreter instead of a compiler.
The programming system we use is depicted in Figure 3.3. The input to our pro-
3-6 CHAPTER 3. PROGRAMMING
' ? $
' ? $
Although Scheme is not widely used in industry, it is a great language for learn-
ing 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 you will learn nearly the entire language by the end of this chapter
(we defer describing a few aspects until Chapter ??), and by the end of this book
you will know enough to implement your own Scheme interpreter. By contrast,
programming languages such as C++ and Java require thousands of pages to de-
scribe, and even the world’s experts in those languages do not agree on exactly
what all programs mean.
8
Originally, it was named “Schemer”, but the machine used to develop it only supported 6-
letter file names, so the name was shortened to “Scheme”
3.4. EXPRESSIONS 3-7
3.4 Expressions
3.4.1 Primitives
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—
numbers, Booleans, and primitive procedures—are described next.
Numbers. Numbers represent numerical values. Scheme provides all the kinds
of numbers you are familiar with, and they mean almost exactly what you think
they mean9 .
Example numbers include:
150 0 -12
9
The details of managing numbers on computers are complex, and we do not consider them
here.
3-8 CHAPTER 3. PROGRAMMING
Numbers evaluate to their value. For example, the value of the primitive expres-
sion 150 is 150.10
Booleans. Booleans represent truth values. There are two primitives for repre-
senting true and false:
PrimitiveExpression ::⇒ #t | #f
Most of the actual work done by a Scheme program is done by application expres-
sions. The grammar rule for application is:
10
By convention, we use the typewriter font to show program text, and the slanted font to
show values. In the DrScheme interactions window, values are shown in blue.
3.4. EXPRESSIONS 3-9
Table 3.1: Selected Scheme Primitive Procedures. Some of these are defined for
more inputs than just the ones shown here. For example, the - procedure works
on one number and on more than two numbers.
3-10 CHAPTER 3. PROGRAMMING
The value of the first expression should be a procedure. All of the primitive pro-
cedures are procedures; in Section 3.6, we will see how to create new procedures.
The remaining expressions are the inputs to the procedure.
For example, the expression (+ 1 2) is an application expression, consisting
of three subexpressions. Although this example is probably simple enough that
you can probably guess that it evaluates to 3, we will demonstrate in detail how
it is evaluated by breaking down into its subexpressions using the grammar rules.
The same process will allow us to understand how any complex expression is
evaluated.
Here is a parse tree for the expression:
Expression
ApplicationExpression
eelY\Y\Y\\\
eeeell YYY\Y\Y\Y\\\\\\\\\\\\\
eeeeee lll YYYYYY \\\\\\\\\\\\\\
eeeeee llll YYY \\\\\\\\
( Expression MoreExpressions
lYYY
)
l YYYYYY
llll YYYYYY
llll YYY
PrimitiveExpression Expression MoreExpressions
lYYY
l YYYYYY
llll YYYYYY
llll YYY
+ PrimitiveExpression Expression MoreExpressions
1 PrimitiveExpression
replaced with anything that appears on the right side of an expression rule, includ-
ing the application expression rule. Hence, we can build up complex expressions
like this:
(+ (* 10 10) (+ 25 25))
Expression
ApplicationExpression
eelY\Y\Y\\\
eeeell YYY\Y\Y\Y\\\\\\\\\\\\\
eeeeee lll YYYYYY \\\\\\\\\\\\\\
eeeeee llll YYY \\\\\\\\
( Expression MoreExpressions
lYYY
)
l YYYYYY
lllllll YYYYYY
YYY
l
PrimitiveExpression Expression MoreExpressions
lYYY
l YYYYYY
llll YYYYYY
llll YYY
+ ApplicationExpression
M Expression MoreExpressions
qM
qqq MMMMM
qqq
(* 10 10) ApplicationExpression
M
qM
qqqqq MMMMM
q
(+ 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 ap-
plication expressions. (We have not shown the complete parse tree for the nested
application expressions, instead denoting them using triangles.)
To evaluate the output application, we need to evaluate all the subexpressions. The
first subexpression, +, evaluates to the primitive procedure. The second subex-
pression, (* 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: (+ (* 10 10) (+ 25 25)) evaluates
to 150.
Exercise 3.1. Predict how each of the following Scheme expressions is evalu-
ated. After making your prediction, try evaluating the expression in DrScheme.
3-12 CHAPTER 3. PROGRAMMING
If the result is different from your prediction, explain why the Scheme interpreter
evaluates the expression as it does.
a. 150
b. (+ 150)
f. +
g. () (+ + <)
3.5 Definitions
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, and yikes!%@#.12
After a name has been bound to a value by a definition, that name may be used in
an expression:
For example,
3.6 Procedures
For the rest of this chapter, however, we will view procedures as idealized mathe-
matical functions: we will consider only procedures that involve no state, and we
will not worry about the resources our procedures require.
Scheme provides a general mechanism for making a procedure. The syntax is:
For a procedure to be useful, we need to apply it. In Section 3.4.2, we saw the
syntax and evaluation rule for an ApplicationExpression when the procedure to be
applied is a primitive procedure. The syntax for applying a constructed procedure
is identical to the syntax for applying a primitive procedure.
We need a new rule, though, for evaluating the application. In this case, the first
Expression evaluates to a procedure that was created using a ProcedureExpres-
sion, so we can think of the ApplicationExpression as (the underlined part is the
replacement for the ProcedureExpression):
ApplicationExpression ::⇒
((lambda (Parameters)Expression) MoreExpressions)
all the subeexpressions and apply the value of the first subexpression to the values
of the remaining subexpressions. The first subexpression evaluates to a procedure
that takes one parameter named x and has the expression body (* x x). There
is one operand expression, the primitive 2, that evaluates to 2.
To evaluate the application we bind the first parameter, x, to the value of the
first operand, 2, and evaluate the procedure body, (* x x). After substituting
the parameter values, we have (* 2 2). This is an application of the primitive
multiplication procedure. Evaluating the application results in the value 4.
The procedure in our example, (lambda (x) (* x x)), is a procedure that
takes a number as input and as output produces the square of that number. We can
use the definition mechanism (from Section 3.5) to give this procedure a name so
we can reuse it:
This defines the name square as the procedure. After this, we can use square
to produce the square of any number:
> (square 2)
4
> (square 1/4)
1/16
> (square (square 2))
16
This is incorporates the lambda invisibly into the definition, but means exactly
the same thing. For example,
Exercise 3.2.
a. Define a procedure, cube, that takes one number as input and produces as
output the cube of that number.
b. Define a procedure, compute-cost, that takes as input two numbers, 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 time the sales tax rate. For
example, (compute-cost 13 0.05) should evaluate to 13.65.
3.7 Decisions
We would like to be able to make procedures where the actions taken depend on
the input values. For example, we may want a procedure that takes two numbers
as inputs and evaluates to the maximum value of the two inputs. To define such a
procedure we need a way of making a decision. A predicate is a test expression
that is used to determine which actions to take next. Scheme provides the if
expression for determining actions based on a predicate.
The IfExpression replacement has three Expression terms. For clarity, we give
each of them names as denoted by the subscripts:
The evaluation rule for an IfExpression is to first evaluate the predicate expression.
If it evaluates to any non-false value, the value of the IfExpression is the value of
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 the alternate 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.
Note that if the value of the predicate is anything other than false (#f), the con-
sequent expression is used. For example, if the predicate evaluates to #t, to a
number, or to a procedure the consequence expression is evaluated.
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
a special evaluation rule for if expressions. The reason a special rule is needed
is because we do not want all the subexpressions to be evaluated. With the nor-
mal application rule, all the subexpressions are evaluated, and then the procedure
resulting from the first subexpression is applied to the values resulting from the
others. With the if special form evaluation rule, the predicate expression is always
evaluated, but 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)
This defines the name max as the value of evaluating the procedure expression,
This is a procedure that takes two inputs, named a and b. Its body is an if expres-
sion 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 #t if the value of the first parameter is
greater, and #f otherwise. According to the evaluation rule for an if expression, if
the predicate evaluates to any non-false value (in this case, #t), the value of the if
expression is the value of the consequent expression, a. If the predicate evaluates
to #f, the value of the if expression is the value of the alternate expression, b.
Hence, our max procedure takes two numbers as inputs and produces as output
the greater of the two inputs.
Exercise 3.3. () Define a procedure, max3, that takes three inputs, and produces
as output the maximum value of the three inputs. For example,
(max3 5 7 3)
should evaluate to 7.
3.8 Summary
At this point, we have covered enough of Scheme to write useful programs. In fact
(as we will see in Chapter ??), we have covered enough to express every possible
computation!
Here we summarize the grammar rules and evaluation rules. Each grammar rule
has an associated evaluation rule. This means that any Scheme fragment that can
be described by the grammar also has an associated meaning that can be produced
by combining the evaluation rules corresponding to the grammar rules.
The evaluation rule for an application (Rule 3b) uses apply to perform the appli-
cation. We define apply using the two application rules:
Note that evaluate in the Application Rule 2 means use the evaluation rules above
to evaluate the expression. Thus, the evaluation rules are defined using the appli-
cation rules, which are defined using the evaluation rules! This appears to be a
3-22 CHAPTER 3. PROGRAMMING
circular definition, but as with the grammar examples, it has a base case. There
are some expressions we can evaluate without using the application rules (e.g.,
primitive expressions, name expressions), and some applications we can evaluate
without using the evaluation rules (when the procedure to apply is a primitive).
Hence, the process of evaluating an expression will sometimes finish and when it
does we end with the value of the expression.15
Exercise 3.4. Follow the evaluation and application rules to evaluate the following
Scheme expression:
(max 3 4)
It is fairly tedious to follow all of the steps (that’s why we normally rely on com-
puters to do it!), but worth doing once to make sure you understand the evaluation
rules.
Exercise 3.5. Define a procedure, abs, that takes a number as input and produces
the absolute value of that number as its output. For example, (abs 3) should
evaluate to 3, (abs -150) should evaluate to 150, and (abs 0) should eval-
uate to 0.
15
This doesn’t guarantee it will always finish, however! We will see in some examples in the
next chapter where evaluation never finishes.