• 沒有找到結果。

Example: Square Roots by Newton’s Method

在文檔中 second edition (頁 56-68)

Building Abstractions with Procedures

1.1 The Elements of Programming

1.1.7 Example: Square Roots by Newton’s Method

Procedures, as introduced above, are much like ordinary mathematical functions. ey specify a value that is determined by one or more pa-rameters. But there is an important difference between mathematical functions and computer procedures. Procedures must be effective.

As a case in point, consider the problem of computing square roots.

We can define the square-root function as

x = the y such that y ≥ 0 and y2= x.

is describes a perfectly legitimate mathematical function. We could use it to recognize whether one number is the square root of another, or to derive facts about square roots in general. On the other hand, the definition does not describe a procedure. Indeed, it tells us almost noth-ing about how to actually find the square root of a given number. It will not help maers to rephrase this definition in pseudo-Lisp:

(define (sqrt x) (the y (and (>= y 0)

(= (square y) x))))

is only begs the question.

e contrast between function and procedure is a reflection of the general distinction between describing properties of things and describ-ing how to do thdescrib-ings, or, as it is sometimes referred to, the distinction

between declarative knowledge and imperative knowledge. In mathe-matics we are usually concerned with declarative (what is) descriptions, whereas in computer science we are usually concerned with imperative (how to) descriptions.20

How does one compute square roots? e most common way is to use Newton’s method of successive approximations, which says that whenever we have a guess y for the value of the square root of a number x, we can perform a simple manipulation to get a beer guess (one closer to the actual square root) by averaging y with x/y.21 For example, we can compute the square root of 2 as follows. Suppose our initial guess is 1:

Guess Quotient Average

1 (2/1) = 2 ((2 + 1)/2) = 1.5

1.5 (2/1.5) = 1.3333 ((1.3333 + 1.5)/2) = 1.4167 1.4167 (2/1.4167) = 1.4118 ((1.4167 + 1.4118)/2) = 1.4142

1.4142 ... ...

20Declarative and imperative descriptions are intimately related, as indeed are math-ematics and computer science. For instance, to say that the answer produced by a pro-gram is “correct” is to make a declarative statement about the propro-gram. ere is a large amount of research aimed at establishing techniques for proving that programs are correct, and much of the technical difficulty of this subject has to do with negotiating the transition between imperative statements (from which programs are constructed) and declarative statements (which can be used to deduce things). In a related vein, an important current area in programming-language design is the exploration of so-called very high-level languages, in which one actually programs in terms of declarative state-ments. e idea is to make interpreters sophisticated enough so that, given “what is”

knowledge specified by the programmer, they can generate “how to” knowledge auto-matically. is cannot be done in general, but there are important areas where progress has been made. We shall revisit this idea inChapter 4.

21is square-root algorithm is actually a special case of Newton’s method, which is a general technique for finding roots of equations. e square-root algorithm itself was developed by Heron of Alexandria in the first century .. We will see how to express the general Newton’s method as a Lisp procedure inSection 1.3.4.

Continuing this process, we obtain beer and beer approximations to the square root.

Now let’s formalize the process in terms of procedures. We start with a value for the radicand (the number whose square root we are trying to compute) and a value for the guess. If the guess is good enough for our purposes, we are done; if not, we must repeat the process with an improved guess. We write this basic strategy as a procedure:

(define (sqrt-iter guess x) (if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

A guess is improved by averaging it with the quotient of the radicand and the old guess:

(define (improve guess x) (average guess (/ x guess)))

where

(define (average x y) (/ (+ x y) 2))

We also have to say what we mean by “good enough.” e following will do for illustration, but it is not really a very good test. (SeeExercise 1.7.) e idea is to improve the answer until it is close enough so that its square differs from the radicand by less than a predetermined tolerance (here 0.001):22

(define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001))

22We will usually give predicates names ending with question marks, to help us re-member that they are predicates. is is just a stylistic convention. As far as the inter-preter is concerned, the question mark is just an ordinary character.

Finally, we need a way to get started. For instance, we can always guess that the square root of any number is 1:23

(define (sqrt x) (sqrt-iter 1.0 x))

If we type these definitions to the interpreter, we can usesqrtjust as we can use any procedure:

(sqrt 9)

3.00009155413138

(sqrt (+ 100 37)) 11.704699917758145

(sqrt (+ (sqrt 2) (sqrt 3))) 1.7739279023207892

(square (sqrt 1000)) 1000.000369924366

esqrtprogram also illustrates that the simple procedural language we have introduced so far is sufficient for writing any purely numeri-cal program that one could write in, say, C or Pasnumeri-cal. is might seem surprising, since we have not included in our language any iterative

23Observe that we express our initial guess as 1.0 rather than 1. is would not make any difference in many Lisp implementations.  Scheme, however, distinguishes be-tween exact integers and decimal values, and dividing two integers produces a rational number rather than a decimal. For example, dividing 10 by 6 yields 5/3, while dividing 10.0 by 6.0 yields 1.6666666666666667. (We will learn how to implement arithmetic on rational numbers inSection 2.1.1.) If we start with an initial guess of 1 in our square-root program, and x is an exact integer, all subsequent values produced in the square-root computation will be rational numbers rather than decimals. Mixed operations on ratio-nal numbers and decimals always yield decimals, so starting with an initial guess of 1.0 forces all subsequent values to be decimals.

(looping) constructs that direct the computer to do something over and over again.sqrt-iter, on the other hand, demonstrates how iteration can be accomplished using no special construct other than the ordinary ability to call a procedure.24

Exercise 1.6:Alyssa P. Hacker doesn’t see whyifneeds to be provided as a special form. “Why can’t I just define it as an ordinary procedure in terms ofcond?” she asks. Alyssa’s friend Eva Lu Ator claims this can indeed be done, and she defines a new version ofif:

(define (new-if predicate then-clause else-clause) (cond (predicate then-clause)

(else else-clause)))

Eva demonstrates the program for Alyssa:

(new-if (= 2 3) 0 5) 5

(new-if (= 1 1) 0 5) 0

Delighted, Alyssa usesnew-ifto rewrite the square-root program:

(define (sqrt-iter guess x) (new-if (good-enough? guess x)

guess

(sqrt-iter (improve guess x) x)))

What happens when Alyssa aempts to use this to compute square roots? Explain.

24Readers who are worried about the efficiency issues involved in using procedure calls to implement iteration should note the remarks on “tail recursion” inSection 1.2.1.

Exercise 1.7: e good-enough? test used in computing square roots will not be very effective for finding the square roots of very small numbers. Also, in real computers, arith-metic operations are almost always performed with lim-ited precision. is makes our test inadequate for very large numbers. Explain these statements, with examples showing how the test fails for small and large numbers. An alterna-tive strategy for implementinggood-enough?is to watch howguesschanges from one iteration to the next and to stop when the change is a very small fraction of the guess.

Design a square-root procedure that uses this kind of end test. Does this work beer for small and large numbers?

Exercise 1.8:Newton’s method for cube roots is based on the fact that if y is an approximation to the cube root of x, then a beer approximation is given by the value

x/y2+ 2y

3 .

Use this formula to implement a cube-root procedure anal-ogous to the square-root procedure. (InSection 1.3.4we will see how to implement Newton’s method in general as an abstraction of these square-root and cube-root procedures.) 1.1.8 Procedures as Black-Box Abstractions

sqrtis our first example of a process defined by a set of mutually defined procedures. Notice that the definition ofsqrt-iteris recursive; that is, the procedure is defined in terms of itself. e idea of being able to define a procedure in terms of itself may be disturbing; it may seem

sqrt | sqrt-iter / \ good-enough improve / \ \ square abs average

Figure 1.2:Procedural decomposition of thesqrtprogram.

unclear how such a “circular” definition could make sense at all, much less specify a well-defined process to be carried out by a computer. is will be addressed more carefully inSection 1.2. But first let’s consider some other important points illustrated by thesqrtexample.

Observe that the problem of computing square roots breaks up nat-urally into a number of subproblems: how to tell whether a guess is good enough, how to improve a guess, and so on. Each of these tasks is accomplished by a separate procedure. e entiresqrtprogram can be viewed as a cluster of procedures (shown inFigure 1.2) that mirrors the decomposition of the problem into subproblems.

e importance of this decomposition strategy is not simply that one is dividing the program into parts. Aer all, we could take any large program and divide it into parts—the first ten lines, the next ten lines, the next ten lines, and so on. Rather, it is crucial that each procedure ac-complishes an identifiable task that can be used as a module in defining other procedures. For example, when we define thegood-enough? pro-cedure in terms of square, we are able to regard thesquareprocedure as a “black box.” We are not at that moment concerned with how the procedure computes its result, only with the fact that it computes the square. e details of how the square is computed can be suppressed, to be considered at a later time. Indeed, as far as thegood-enough?

pro-cedure is concerned,squareis not quite a procedure but rather an ab-straction of a procedure, a so-called procedural abab-straction. At this level of abstraction, any procedure that computes the square is equally good.

us, considering only the values they return, the following two procedures for squaring a number should be indistinguishable. Each takes a numerical argument and produces the square of that number as the value.25

(define (square x) (* x x))

(define (square x) (exp (double (log x)))) (define (double x) (+ x x))

So a procedure definition should be able to suppress detail. e users of the procedure may not have wrien the procedure themselves, but may have obtained it from another programmer as a black box. A user should not need to know how the procedure is implemented in order to use it.

Local names

One detail of a procedure’s implementation that should not maer to the user of the procedure is the implementer’s choice of names for the procedure’s formal parameters. us, the following procedures should not be distinguishable:

(define (square x) (* x x)) (define (square y) (* y y))

25It is not even clear which of these procedures is a more efficient implementation.

is depends upon the hardware available. ere are machines for which the “obvious”

implementation is the less efficient one. Consider a machine that has extensive tables of logarithms and antilogarithms stored in a very efficient manner.

is principle—that the meaning of a procedure should be independent of the parameter names used by its author—seems on the surface to be self-evident, but its consequences are profound. e simplest conse-quence is that the parameter names of a procedure must be local to the body of the procedure. For example, we usedsquarein the definition ofgood-enough?in our square-root procedure:

(define (good-enough? guess x) (< (abs (- (square guess) x))

0.001))

e intention of the author ofgood-enough?is to determine if the square of the first argument is within a given tolerance of the second argument.

We see that the author ofgood-enough?used the nameguessto refer to the first argument andxto refer to the second argument. e argument ofsquareisguess. If the author ofsquareusedx(as above) to refer to that argument, we see that thexingood-enough?must be a differentx than the one insquare. Running the proceduresquaremust not affect the value of xthat is used bygood-enough?, because that value of x may be needed bygood-enough?aersquareis done computing.

If the parameters were not local to the bodies of their respective procedures, then the parameterxinsquarecould be confused with the parameterxingood-enough?, and the behavior ofgood-enough?would depend upon which version ofsquarewe used. us,squarewould not be the black box we desired.

A formal parameter of a procedure has a very special role in the procedure definition, in that it doesn’t maer what name the formal parameter has. Such a name is called a bound variable, and we say that the procedure definition binds its formal parameters. e meaning of a procedure definition is unchanged if a bound variable is consistently

renamed throughout the definition.26If a variable is not bound, we say that it is free. e set of expressions for which a binding defines a name is called the scope of that name. In a procedure definition, the bound variables declared as the formal parameters of the procedure have the body of the procedure as their scope.

In the definition of good-enough? above,guess andx are bound variables but <, -, abs, and square are free. e meaning of

good-enough?should be independent of the names we choose forguessand

xso long as they are distinct and different from<,-,abs, andsquare. (If we renamedguesstoabswe would have introduced a bug by cap-turingthe variableabs. It would have changed from free to bound.) e meaning of good-enough?is not independent of the names of its free variables, however. It surely depends upon the fact (external to this def-inition) that the symbolabsnames a procedure for computing the abso-lute value of a number.good-enough?will compute a different function if we substitutecosforabsin its definition.

Internal definitions and block structure

We have one kind of name isolation available to us so far: e formal parameters of a procedure are local to the body of the procedure. e square-root program illustrates another way in which we would like to control the use of names. e existing program consists of separate procedures:

(define (sqrt x) (sqrt-iter 1.0 x)) (define (sqrt-iter guess x)

(if (good-enough? guess x)

26e concept of consistent renaming is actually subtle and difficult to define for-mally. Famous logicians have made embarrassing errors here.

guess

(sqrt-iter (improve guess x) x))) (define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001)) (define (improve guess x)

(average guess (/ x guess)))

e problem with this program is that the only procedure that is impor-tant to users ofsqrtissqrt. e other procedures (sqrt-iter,

good-enough?, andimprove) only cluer up their minds. ey may not define any other procedure calledgood-enough?as part of another program to work together with the square-root program, becausesqrtneeds it.

e problem is especially severe in the construction of large systems by many separate programmers. For example, in the construction of a large library of numerical procedures, many numerical functions are computed as successive approximations and thus might have proce-dures namedgood-enough?andimproveas auxiliary procedures. We would like to localize the subprocedures, hiding them insidesqrt so thatsqrtcould coexist with other successive approximations, each hav-ing its own privategood-enough?procedure. To make this possible, we allow a procedure to have internal definitions that are local to that pro-cedure. For example, in the square-root problem we can write

(define (sqrt x)

(define (good-enough? guess x)

(< (abs (- (square guess) x)) 0.001))

(define (improve guess x) (average guess (/ x guess))) (define (sqrt-iter guess x)

(if (good-enough? guess x) guess

(sqrt-iter (improve guess x) x))) (sqrt-iter 1.0 x))

Such nesting of definitions, called block structure, is basically the right solution to the simplest name-packaging problem. But there is a bet-ter idea lurking here. In addition to inbet-ternalizing the definitions of the auxiliary procedures, we can simplify them. Sincexis bound in the defi-nition ofsqrt, the proceduresgood-enough?,improve, andsqrt-iter, which are defined internally tosqrt, are in the scope of x. us, it is not necessary to passxexplicitly to each of these procedures. Instead, we allowxto be a free variable in the internal definitions, as shown be-low. enxgets its value from the argument with which the enclosing proceduresqrtis called. is discipline is called lexical scoping.27

(define (sqrt x)

(define (good-enough? guess)

(< (abs (- (square guess) x)) 0.001)) (define (improve guess)

(average guess (/ x guess))) (define (sqrt-iter guess)

(if (good-enough? guess) guess

(sqrt-iter (improve guess)))) (sqrt-iter 1.0))

We will use block structure extensively to help us break up large pro-grams into tractable pieces.28e idea of block structure originated with the programming language Algol 60. It appears in most advanced pro-gramming languages and is an important tool for helping to organize the construction of large programs.

27Lexical scoping dictates that free variables in a procedure are taken to refer to bindings made by enclosing procedure definitions; that is, they are looked up in the environment in which the procedure was defined. We will see how this works in detail in chapter 3 when we study environments and the detailed behavior of the interpreter.

28Embedded definitions must come first in a procedure body. e management is not responsible for the consequences of running programs that intertwine definition and

在文檔中 second edition (頁 56-68)