Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

(03 - 3) Lectura I - FP

Download as pdf or txt
Download as pdf or txt
You are on page 1of 13

1.

1 The Elements of Programming


A powerful programming language is more than just a means for in-
structing a computer to perform tasks. e language also serves as a
framework within which we organize our ideas about processes. us,
when we describe a language, we should pay particular aention to the
means that the language provides for combining simple ideas to form
more complex ideas. Every powerful language has three mechanisms
for accomplishing this:

• primitive expressions, which represent the simplest entities the


language is concerned with,

• means of combination, by which compound elements are built


from simpler ones, and

• means of abstraction, by which compound elements can be named


and manipulated as units.

In programming, we deal with two kinds of elements: procedures and


data. (Later we will discover that they are really not so distinct.) Infor-
mally, data is “stuff” that we want to manipulate, and procedures are
descriptions of the rules for manipulating the data. us, any powerful
programming language should be able to describe primitive data and
primitive procedures and should have methods for combining and ab-
stracting procedures and data.
In this chapter we will deal only with simple numerical data so that
we can focus on the rules for building procedures.4 In later chapters we
4
e characterization of numbers as “simple data” is a barefaced bluff. In fact, the
treatment of numbers is one of the trickiest and most confusing aspects of any pro-

6
will see that these same rules allow us to build procedures to manipulate
compound data as well.

1.1.1 Expressions
One easy way to get started at programming is to examine some typical
interactions with an interpreter for the Scheme dialect of Lisp. Imagine
that you are siing at a computer terminal. You type an expression, and
the interpreter responds by displaying the result of its evaluating that
expression.
One kind of primitive expression you might type is a number. (More
precisely, the expression that you type consists of the numerals that
represent the number in base 10.) If you present Lisp with a number
486

the interpreter will respond by printing5


486

gramming language. Some typical issues involved are these: Some computer systems
distinguish integers, such as 2, from real numbers, such as 2.71. Is the real number 2.00
different from the integer 2? Are the arithmetic operations used for integers the same
as the operations used for real numbers? Does 6 divided by 2 produce 3, or 3.0? How
large a number can we represent? How many decimal places of accuracy can we repre-
sent? Is the range of integers the same as the range of real numbers? Above and beyond
these questions, of course, lies a collection of issues concerning roundoff and trunca-
tion errors—the entire science of numerical analysis. Since our focus in this book is on
large-scale program design rather than on numerical techniques, we are going to ignore
these problems. e numerical examples in this chapter will exhibit the usual roundoff
behavior that one observes when using arithmetic operations that preserve a limited
number of decimal places of accuracy in noninteger operations.
5
roughout this book, when we wish to emphasize the distinction between the
input typed by the user and the response printed by the interpreter, we will show the
laer in slanted characters.

7
Expressions representing numbers may be combined with an expres-
sion representing a primitive procedure (such as + or *) to form a com-
pound expression that represents the application of the procedure to
those numbers. For example:
(+ 137 349)
486

(- 1000 334)
666

(* 5 99)
495

(/ 10 5)
2

(+ 2.7 10)
12.7

Expressions such as these, formed by delimiting a list of expressions


within parentheses in order to denote procedure application, are called
combinations. e lemost element in the list is called the operator, and
the other elements are called operands. e value of a combination is
obtained by applying the procedure specified by the operator to the ar-
guments that are the values of the operands.
e convention of placing the operator to the le of the operands
is known as prefix notation, and it may be somewhat confusing at first
because it departs significantly from the customary mathematical con-
vention. Prefix notation has several advantages, however. One of them
is that it can accommodate procedures that may take an arbitrary num-
ber of arguments, as in the following examples:

8
(+ 21 35 12 7)
75

(* 25 4 12)
1200

No ambiguity can arise, because the operator is always the lemost el-
ement and the entire combination is delimited by the parentheses.
A second advantage of prefix notation is that it extends in a straight-
forward way to allow combinations to be nested, that is, to have combi-
nations whose elements are themselves combinations:
(+ (* 3 5) (- 10 6))
19

ere is no limit (in principle) to the depth of such nesting and to the
overall complexity of the expressions that the Lisp interpreter can eval-
uate. It is we humans who get confused by still relatively simple expres-
sions such as
(+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))

which the interpreter would readily evaluate to be 57. We can help our-
selves by writing such an expression in the form
(+ (* 3
(+ (* 2 4)
(+ 3 5)))
(+ (- 10 7)
6))

following a formaing convention known as prey-printing, in which


each long combination is wrien so that the operands are aligned ver-
tically. e resulting indentations display clearly the structure of the

9
expression.6
Even with complex expressions, the interpreter always operates in
the same basic cycle: It reads an expression from the terminal, evaluates
the expression, and prints the result. is mode of operation is oen
expressed by saying that the interpreter runs in a read-eval-print loop.
Observe in particular that it is not necessary to explicitly instruct the
interpreter to print the value of the expression.7

1.1.2 Naming and the Environment


A critical aspect of a programming language is the means it provides
for using names to refer to computational objects. We say that the name
identifies a variable whose value is the object.
In the Scheme dialect of Lisp, we name things with define. Typing
(define size 2)

causes the interpreter to associate the value 2 with the name size.8
Once the name size has been associated with the number 2, we can
refer to the value 2 by name:
size
2

6
Lisp systems typically provide features to aid the user in formaing expressions.
Two especially useful features are one that automatically indents to the proper prey-
print position whenever a new line is started and one that highlights the matching le
parenthesis whenever a right parenthesis is typed.
7
Lisp obeys the convention that every expression has a value. is convention, to-
gether with the old reputation of Lisp as an inefficient language, is the source of the
quip by Alan Perlis (paraphrasing Oscar Wilde) that “Lisp programmers know the value
of everything but the cost of nothing.”
8
In this book, we do not show the interpreter’s response to evaluating definitions,
since this is highly implementation-dependent.

10
(* 5 size)
10

Here are further examples of the use of define:


(define pi 3.14159)
(define radius 10)
(* pi (* radius radius))
314.159
(define circumference (* 2 pi radius))
circumference
62.8318

define is our language’s simplest means of abstraction, for it allows


us to use simple names to refer to the results of compound operations,
such as the circumference computed above. In general, computational
objects may have very complex structures, and it would be extremely
inconvenient to have to remember and repeat their details each time we
want to use them. Indeed, complex programs are constructed by build-
ing, step by step, computational objects of increasing complexity. e
interpreter makes this step-by-step program construction particularly
convenient because name-object associations can be created incremen-
tally in successive interactions. is feature encourages the incremental
development and testing of programs and is largely responsible for the
fact that a Lisp program usually consists of a large number of relatively
simple procedures.
It should be clear that the possibility of associating values with sym-
bols and later retrieving them means that the interpreter must maintain
some sort of memory that keeps track of the name-object pairs. is
memory is called the environment (more precisely the global environ-
ment, since we will see later that a computation may involve a number

11
of different environments).9

1.1.3 Evaluating Combinations


One of our goals in this chapter is to isolate issues about thinking pro-
cedurally. As a case in point, let us consider that, in evaluating combi-
nations, the interpreter is itself following a procedure.
To evaluate a combination, do the following:

1. Evaluate the subexpressions of the combination.

2. Apply the procedure that is the value of the lemost subexpres-


sion (the operator) to the arguments that are the values of the
other subexpressions (the operands).

Even this simple rule illustrates some important points about processes
in general. First, observe that the first step dictates that in order to ac-
complish the evaluation process for a combination we must first per-
form the evaluation process on each element of the combination. us,
the evaluation rule is recursive in nature; that is, it includes, as one of
its steps, the need to invoke the rule itself.10
Notice how succinctly the idea of recursion can be used to express
what, in the case of a deeply nested combination, would otherwise be
viewed as a rather complicated process. For example, evaluating
9
Chapter 3 will show that this notion of environment is crucial, both for under-
standing how the interpreter works and for implementing interpreters.
10
It may seem strange that the evaluation rule says, as part of the first step, that
we should evaluate the lemost element of a combination, since at this point that can
only be an operator such as + or * representing a built-in primitive procedure such as
addition or multiplication. We will see later that it is useful to be able to work with
combinations whose operators are themselves compound expressions.

12
390

* 26 15

+ 2 24
+ 3 5 7

* 4 6

Figure 1.1: Tree representation, showing the value of each


subcombination.

(* (+ 2 (* 4 6))
(+ 3 5 7))

requires that the evaluation rule be applied to four different combina-


tions. We can obtain a picture of this process by representing the combi-
nation in the form of a tree, as shown in Figure 1.1. Each combination is
represented by a node with branches corresponding to the operator and
the operands of the combination stemming from it. e terminal nodes
(that is, nodes with no branches stemming from them) represent either
operators or numbers. Viewing evaluation in terms of the tree, we can
imagine that the values of the operands percolate upward, starting from
the terminal nodes and then combining at higher and higher levels. In
general, we shall see that recursion is a very powerful technique for
dealing with hierarchical, treelike objects. In fact, the “percolate values
upward” form of the evaluation rule is an example of a general kind of
process known as tree accumulation.
Next, observe that the repeated application of the first step brings us
to the point where we need to evaluate, not combinations, but primitive
expressions such as numerals, built-in operators, or other names. We

13
take care of the primitive cases by stipulating that

• the values of numerals are the numbers that they name,

• the values of built-in operators are the machine instruction se-


quences that carry out the corresponding operations, and

• the values of other names are the objects associated with those
names in the environment.

We may regard the second rule as a special case of the third one by stip-
ulating that symbols such as + and * are also included in the global envi-
ronment, and are associated with the sequences of machine instructions
that are their “values.” e key point to notice is the role of the environ-
ment in determining the meaning of the symbols in expressions. In an
interactive language such as Lisp, it is meaningless to speak of the value
of an expression such as (+ x 1) without specifying any information
about the environment that would provide a meaning for the symbol
x (or even for the symbol +). As we shall see in Chapter 3, the general
notion of the environment as providing a context in which evaluation
takes place will play an important role in our understanding of program
execution.
Notice that the evaluation rule given above does not handle defini-
tions. For instance, evaluating (define x 3) does not apply define to
two arguments, one of which is the value of the symbol x and the other
of which is 3, since the purpose of the define is precisely to associate x
with a value. (at is, (define x 3) is not a combination.)
Such exceptions to the general evaluation rule are called special forms.
define is the only example of a special form that we have seen so far,
but we will meet others shortly. Each special form has its own evalu-
ation rule. e various kinds of expressions (each with its associated

14
evaluation rule) constitute the syntax of the programming language. In
comparison with most other programming languages, Lisp has a very
simple syntax; that is, the evaluation rule for expressions can be de-
scribed by a simple general rule together with specialized rules for a
small number of special forms.11

1.1.4 Compound Procedures


We have identified in Lisp some of the elements that must appear in any
powerful programming language:

• Numbers and arithmetic operations are primitive data and proce-


dures.

• Nesting of combinations provides a means of combining opera-


tions.

• Definitions that associate names with values provide a limited


means of abstraction.

Now we will learn about procedure definitions, a much more powerful


abstraction technique by which a compound operation can be given a
name and then referred to as a unit.
11
Special syntactic forms that are simply convenient alternative surface structures
for things that can be wrien in more uniform ways are sometimes called syntactic
sugar, to use a phrase coined by Peter Landin. In comparison with users of other lan-
guages, Lisp programmers, as a rule, are less concerned with maers of syntax. (By
contrast, examine any Pascal manual and notice how much of it is devoted to descrip-
tions of syntax.) is disdain for syntax is due partly to the flexibility of Lisp, which
makes it easy to change surface syntax, and partly to the observation that many “con-
venient” syntactic constructs, which make the language less uniform, end up causing
more trouble than they are worth when programs become large and complex. In the
words of Alan Perlis, “Syntactic sugar causes cancer of the semicolon.”

15
We begin by examining how to express the idea of “squaring.” We
might say, “To square something, multiply it by itself.” is is expressed
in our language as
(define (square x) (* x x))

We can understand this in the following way:


(define (square x) (* x x))
| | | | | |
To square something, multiply it by itself.

We have here a compound procedure, which has been given the name
square. e procedure represents the operation of multiplying some-
thing by itself. e thing to be multiplied is given a local name, x, which
plays the same role that a pronoun plays in natural language. Evaluating
the definition creates this compound procedure and associates it with
the name square.12
e general form of a procedure definition is
(define (⟨name⟩ ⟨formal parameters⟩)
⟨ body⟩)
e ⟨name ⟩ is a symbol to be associated with the procedure definition in
the environment.13 e ⟨formal parameters ⟩ are the names used within
the body of the procedure to refer to the corresponding arguments of
the procedure. e ⟨body ⟩ is an expression that will yield the value of
12
Observe that there are two different operations being combined here: we are creat-
ing the procedure, and we are giving it the name square. It is possible, indeed important,
to be able to separate these two notions—to create procedures without naming them,
and to give names to procedures that have already been created. We will see how to do
this in Section 1.3.2.
13 roughout this book, we will describe the general syntax of expressions by using

italic symbols delimited by angle brackets—e.g., ⟨name ⟩—to denote the “slots” in the
expression to be filled in when such an expression is actually used.

16
the procedure application when the formal parameters are replaced by
the actual arguments to which the procedure is applied.14 e ⟨name ⟩
and the ⟨formal parameters ⟩ are grouped within parentheses, just as they
would be in an actual call to the procedure being defined.
Having defined square, we can now use it:
(square 21)
441
(square (+ 2 5))
49
(square (square 3))
81

We can also use square as a building block in defining other procedures.


For example, x 2 + y 2 can be expressed as
(+ (square x) (square y))

We can easily define a procedure sum-of-squares that, given any two


numbers as arguments, produces the sum of their squares:
(define (sum-of-squares x y)
(+ (square x) (square y)))
(sum-of-squares 3 4)
25

Now we can use sum-of-squares as a building block in constructing


further procedures:
(define (f a)
(sum-of-squares (+ a 1) (* a 2)))
(f 5)
136

14 More generally, the body of the procedure can be a sequence of expressions. In this

case, the interpreter evaluates each expression in the sequence in turn and returns the
value of the final expression as the value of the procedure application.

17
Compound procedures are used in exactly the same way as primitive
procedures. Indeed, one could not tell by looking at the definition of
sum-of-squares given above whether square was built into the inter-
preter, like + and *, or defined as a compound procedure.

1.1.5 The Substitution Model for Procedure Application


To evaluate a combination whose operator names a compound proce-
dure, the interpreter follows much the same process as for combina-
tions whose operators name primitive procedures, which we described
in Section 1.1.3. at is, the interpreter evaluates the elements of the
combination and applies the procedure (which is the value of the oper-
ator of the combination) to the arguments (which are the values of the
operands of the combination).
We can assume that the mechanism for applying primitive proce-
dures to arguments is built into the interpreter. For compound proce-
dures, the application process is as follows:

To apply a compound procedure to arguments, evaluate the


body of the procedure with each formal parameter replaced
by the corresponding argument.

To illustrate this process, let’s evaluate the combination


(f 5)

where f is the procedure defined in Section 1.1.4. We begin by retrieving


the body of f:
(sum-of-squares (+ a 1) (* a 2))

en we replace the formal parameter a by the argument 5:


(sum-of-squares (+ 5 1) (* 5 2))

18

You might also like