# embedding_metadata

key: chroma:document
string_value:
exp 1) m))                       m))))   “I don't see what dif ference that could make,” says Louis. “I do.” says Eva. “By writing the procedure like that, you have transformed the θ( log n) process into a θ( n) process.” Explain. Exer cise 1.27.   Demonstrate that the Carmichael numbers listed in footnote 47 really do fool the Fermat test. That is, write a procedure that takes an integer n and tests whether an is congruent to a modulo n for every a<n, and try your procedure on the given Carmichael numbers. Exer cise 1.28.   One variant of the Fermat test that cannot be fooled is called the Miller -Rabin test  (Miller 1976; Rabin 1980). This starts from an alternate form of Fermat's Little Theorem, which states that if n is a prime number and a is any positive integer less than n, then a raised to the ( n - 1)st power is congruent to 1 modulo n. To test the primality of a number n by the Miller -Rabin test, we pick a random number a<n and raise a to the ( n - 1)st power modulo n using the expmod  procedure. H


key: chroma:document
string_value:
owever , whenever we perform the squaring step in expmod , we check to see if we have discovered a “nontrivial square root of 1 modulo n,” that is, a number not equal to 1 or n - 1 whose square is equal to 1 modulo n. It is possible to prove that if such a nontrivial square root of 1 exists, then n is not prime. It is also possible to prove that if n is an odd number that is not prime, then, for at least half thenumbers a<n, computing an-1 in this way will reveal a nontrivial square root of 1 modulo n. (This is why the Miller -Rabin test cannot be fooled.) Modify the expmod  procedure to signal if it discovers a nontrivial square root of 1, and use this to implement the Miller -Rabin test with a procedure analogous to fermat-test . Check your procedure by testing various known primes and non-primes. Hint: One convenient way to make expmod  signal is to have it return 0. 29 In a real program we would probably use the block structure introduced in the last section to hide the definition of fact-iter : (define (


key: chroma:document
string_value:
factorial n)     (define (iter product counter)       (if (> counter n)           product           (iter (* counter product)                 (+ counter 1))))     (iter 1 1))   We avoided doing this here so as to minimize the number of things to think about at once. 30 When we discuss the implementation of procedures on register machines in chapter 5, we will see that any iterative process can be realized “in hardware” as a machine that has a fixed set of registers and no auxiliary memory . In contrast, realizing a recursive process requires a machine that uses an auxiliary data structure known as a stack . 31 Tail recursion has long been known as a compiler optimization trick. A coherent semantic basis for tail recursion was provided by Carl Hewitt (1977), who explained it in terms of the “message- passing” model of computation that we shall discuss in chapter 3. Inspired by this, Gerald Jay Sussman and Guy Lewis Steele Jr . (see Steele 1975) constructed a tail-recursive interpreter for Scheme. Steele later 


key: chroma:document
string_value:
showed how tail recursion is a consequence of the natural way to compile procedure calls (Steele 1977). The IEEE standard for Scheme requires that Scheme implementations be tail-recursive. 32 An example of this was hinted at in section 1.1.3 : The interpreter itself evaluates expressions using a tree-recursive process. 33 For example, work through in detail how the reduction rule applies to the problem of making change for 10 cents using pennies and nickels. 34 One approach to coping with redundant computations is to arrange matters so that we automatically construct a table of values as they are computed. Each time we are asked to apply the procedure to some ar gument, we first look to see if the value is already stored in the table, in which case we avoid performing the redundant computation. This strategy , known as tabulation  or memoization , can be implemented in a straightforward way . Tabulation can sometimes be used to transform processes thatrequire an exponential number of steps (such as count-chan


key: chroma:document
string_value:
ge ) into processes whose space and time requirements grow linearly with the input. See exercise 3.27. 35 The elements of Pascal's triangle are called the binomial coefficients , because the nth row consists of the coef ficients of the terms in the expansion of ( x + y)n. This pattern for computing the coefficients appeared in Blaise Pascal's 1653 seminal work on probability theory , Traité du triangle arithmétique . According to Knuth (1973), the same pattern appears in the Szu-yuen Yü-chien  (“The Precious Mirror of the Four Elements”), published by the Chinese mathematician Chu Shih-chieh in 1303, in the works of the twelfth-century Persian poet and mathematician Omar Khayyam, and in the works of the twelfth-century Hindu mathematician Bháscara Áchárya. 36 These statements mask a great deal of oversimplification. For instance, if we count process steps as “machine operations” we are making the assumption that the number of machine operations needed to perform, say , a multiplication is independent of the s


key: chroma:document
string_value:
ize of the numbers to be multiplied, which is false if the numbers are suf ficiently lar ge. Similar remarks hold for the estimates of space. Like the design and description of a process, the analysis of a process can be carried out at various levels of abstraction. 37 More precisely , the number of multiplications required is equal to 1 less than the log base 2 of n plus the number of ones in the binary representation of n. This total is always less than twice the log base 2 of n. The arbitrary constants k1 and k2 in the definition of order notation imply that, for a logarithmic process, the base to which logarithms are taken does not matter , so all such processes are described as θ( log n). 38 You may wonder why anyone would care about raising numbers to the 1000th power . See section 1.2.6 . 39 This iterative algorithm is ancient. It appears in the Chandah-sutra  by Áchárya Pingala, written before 200 B.C. See Knuth 1981, section 4.6.3, for a full discussion and analysis of this and other methods of expon


key: chroma:document
string_value:
entiation. 40 This algorithm, which is sometimes known as the “Russian peasant method” of multiplication, is ancient. Examples of its use are found in the Rhind Papyrus, one of the two oldest mathematical documents in existence, written about 1700 B.C. (and copied from an even older document) by an Egyptian scribe named A'h-mose. 41 This exercise was suggested to us by Joe Stoy , based on an example in Kaldewaij 1990. 42 Euclid's Algorithm is so called because it appears in Euclid's Elements  (Book 7, ca. 300 B.C.). According to Knuth (1973), it can be considered the oldest known nontrivial algorithm. The ancient Egyptian method of multiplication (exercise 1.18) is surely older , but, as Knuth explains, Euclid's algorithm is the oldest known to have been presented as a general algorithm, rather than as a set of illustrative examples. 43 This theorem was proved in 1845 by Gabriel Lamé, a French mathematician and engineer known chiefly for his contributions to mathematical physics. To prove the theorem, we cons


key: chroma:document
string_value:
ider pairs ( ak ,bk), where ak> bk, for which Euclid's Algorithm terminates in k steps. The proof is based on the claim that, if ( ak+1, bk+1) ⟶ (ak, bk) ⟶ (ak-1, bk-1) are three successive pairs in the reduction process, then we must have bk+1> bk + bk-1. To verify the claim, consider that a reduction step isdefined by applying the transformation ak-1 = bk, bk-1 = remainder of ak divided by bk. The second equation means that ak = qbk + bk-1 for some positive integer q. And since q must be at least 1 we have ak = qbk + bk-1> bk + bk-1. But in the previous reduction step we have bk+1 = ak. Therefore, bk+1 = ak> bk + bk-1. This verifies the claim. Now we can prove the theorem by induction on k, the number of steps that the algorithm requires to terminate. The result is true for k = 1, since this merely requires that b be at least as lar ge as Fib(1) = 1. Now , assume that the result is true for all integers less than or equal to k and establish the result for k + 1. Let ( ak+1, bk+1) ⟶  (ak, bk) ⟶ (ak-1, bk-1) 


key: chroma:document
string_value:
be successive pairs in the reduction process. By our induction hypotheses, we have bk-1> Fib(k - 1) and bk> Fib(k). Thus, applying the claim we just proved together with the definition of the Fibonacci numbers gives bk+1> bk + bk-1> Fib(k) + Fib(k - 1) = Fib(k + 1), which completes the proof of Lamé's Theorem. 44 If d is a divisor of n, then so is n/d. But d and n/d cannot both be greater than √ n. 45 Pierre de Fermat (1601-1665) is considered to be the founder of modern number theory . He obtained many important number -theoretic results, but he usually announced just the results, without providing his proofs. Fermat's Little Theorem was stated in a letter he wrote in 1640. The first published proof was given by Euler in 1736 (and an earlier , identical proof was discovered in the unpublished manuscripts of Leibniz). The most famous of Fermat's results – known as Fermat's Last Theorem – was jotted down in 1637 in his copy of the book Arithmetic  (by the third-century Greek mathematician Diophantus) with the 


key: chroma:document
string_value:
remark “I have discovered a truly remarkable proof, but this margin is too small to contain it.” Finding a proof of Fermat's Last Theorem became one of the most famous challenges in number theory . A complete solution was finally given in 1995 by Andrew Wiles of Princeton University . 46 The reduction steps in the cases where the exponent e is greater than 1 are based on the fact that, for any integers x, y, and m, we can find the remainder of x times y modulo m by computing separately the remainders of x modulo m and y modulo m, multiplying these, and then taking the remainder of the result modulo m. For instance, in the case where e is even, we compute the remainder of be/2 modulo m, square this, and take the remainder modulo m. This technique is useful because it means we can perform our computation without ever having to deal with numbers much larger than m. (Compare exercise 1.25.) 47 Numbers that fool the Fermat test are called Carmichael numbers , and little is known about them other than that they are


key: chroma:document
string_value:
 extremely rare. There are 255 Carmichael numbers below 100,000,000. The smallest few are 561, 1 105, 1729, 2465, 2821, and 6601. In testing primality of very lar ge numbers chosen at random, the chance of stumbling upon a value that fools the Fermat test is less than the chance that cosmic radiation will cause the computer to make an error in carrying out a “correct” algorithm. Considering an algorithm to be inadequate for the first reason but not for the second illustrates the dif ference between mathematics and engineering. 48 One of the most striking applications of probabilistic prime testing has been to the field of cryptography . Although it is now computationally infeasible to factor an arbitrary 200-digit number , the primality of such a number can be checked in a few seconds with the Fermat test. This fact forms the basis of a technique for constructing “unbreakable codes” suggested by Rivest, Shamir , and Adleman (1977). The resulting RSA algorithm  has become a widely used technique for enhancing 


key: chroma:document
string_value:
the security of electronic communications. Because of this and related developments, the study of prime numbers, once considered the epitome of a topic in “pure” mathematics to be studied only for its own sake, now turns out to have important practical applications to cryptography , electronic funds transfer , and information retrieval.1.3  Formulating Abstractions with Higher-Order Procedures We have seen that procedures are, in ef fect, abstractions that describe compound operations on numbers independent of the particular numbers. For example, when we (define (cube x) (* x x x))   we are not talking about the cube of a particular number , but rather about a method for obtaining the cube of any number . Of course we could get along without ever defining this procedure, by always writing expressions such as (* 3 3 3)   (* x x x)   (* y y y)          and never mentioning cube  explicitly . This would place us at a serious disadvantage, forcing us to work always at the level of the particular operations that h


key: chroma:document
string_value:
appen to be primitives in the language (multiplication, in this case) rather than in terms of higher -level operations. Our programs would be able to compute cubes, but our language would lack the ability to express the concept of cubing. One of the things we should demand from a powerful programming language is the ability to build abstractions by assigning names to common patterns and then to work in terms of the abstractions directly . Procedures provide this ability . This is why all but the most primitive programming languages include mechanisms for defining procedures. Yet even in numerical processing we will be severely limited in our ability to create abstractions if we are restricted to procedures whose parameters must be numbers. Often the same programming pattern will be used with a number of dif ferent procedures. To express such patterns as concepts, we will need to construct procedures that can accept procedures as ar guments or return procedures as values. Procedures that manipulate procedures 


key: chroma:document
string_value:
are called higher -order pr ocedur es. This section shows how higher -orderprocedures can serve as powerful abstraction mechanisms, vastly increasing the expressive power of our language. 1.3.1  Pr ocedur es as Arguments Consider the following three procedures. The first computes the sum of the integers from a through b: (define (sum-integers a b)     (if (> a b)         0        (+ a (sum-integers (+ a 1) b))))   The second computes the sum of the cubes of the integers in the given range: (define (sum-cubes a b)     (if (> a b)         0        (+ (cube a) (sum-cubes (+ a 1) b))))   The third computes the sum of a sequence of terms in the series which conver ges to π/8 (very slowly):49 (define (pi-sum a b)     (if (> a b)         0        (+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))   These three procedures clearly share a common underlying pattern. They are for the most part identical, dif fering only in the name of the procedure, the function of a used to compute the term to be added, and the function th


key: chroma:document
string_value:
at provides the next value of a. We could generate each of the procedures by filling in slots in the same template:(define (< name> a b)    (if (> a b)         0        (+ (< term> a)           (< name> (<next> a) b))))   The presence of such a common pattern is strong evidence that there is a useful abstraction waiting to be brought to the surface. Indeed, mathematicians long ago identified the abstraction of summation of a series and invented “sigma notation,” for example to express this concept. The power of sigma notation is that it allows mathematicians to deal with the concept of summation itself rather than only with particular sums – for example, to formulate general results about sums that are independent of the particular series being summed. Similarly , as program designers, we would like our language to be powerful enough so that we can write a procedure that expresses the concept of summation itself rather than only procedures that compute particular sums. We can do so readily in our procedural l


key: chroma:document
string_value:
anguage by taking the common template shown above and transforming the “slots” into formal parameters: (define (sum term a next b)     (if (> a b)         0        (+ (term a)            (sum term (next a) next b))))   Notice that sum takes as its ar guments the lower and upper bounds a and b together with the procedures term  and next . We can use sum just as we would any procedure. For example, we can use it (along with a procedure inc that increments its ar gument by 1) to define sum-cubes : (define (inc n) (+ n 1))   (define (sum-cubes a b)    (sum cube a inc b))   Using this, we can compute the sum of the cubes of the integers from 1 to 10: (sum-cubes 1 10)   3025  With the aid of an identity procedure to compute the term, we can define sum-integers  in terms of sum: (define (identity x) x)     (define (sum-integers a b)     (sum identity a inc b))   Then we can add up the integers from 1 to 10: (sum-integers 1 10)   55  We can also define pi-sum  in the same way:50 (define (pi-sum a b)     (define (pi-t


key: chroma:document
string_value:
erm x)       (/ 1.0 (* x (+ x 2))))     (define (pi-next x)       (+ x 4))     (sum pi-term a pi-next b))   Using these procedures, we can compute an approximation to π: (* 8 (pi-sum 1 1000))   3.139592655589783   Once we have sum, we can use it as a building block in formulating further concepts. For instance, the definite integral of a function f between the limits a and b can be approximated numerically using the formulafor small values of dx. We can express this directly as a procedure: (define (integral f a b dx)     (define (add-dx x) (+ x dx))     (* (sum f (+ a (/ dx 2.0)) add-dx b)        dx))   (integral cube 0 1 0.01)   .24998750000000042   (integral cube 0 1 0.001)   .249999875000001   (The exact value of the integral of cube  between 0 and 1 is 1/4.) Exer cise 1.29.   Simpson's Rule is a more accurate method of numerical integration than the method illustrated above. Using Simpson's Rule, the integral of a function f between a and b is approximated as where h = (b - a)/n, for some even integer n,


key: chroma:document
string_value:
 and yk = f(a + kh). (Increasing n increases the accuracy of the approximation.) Define a procedure that takes as ar guments f, a, b, and n and returns the value of the integral, computed using Simpson's Rule. Use your procedure to integrate cube between 0 and 1 (with n = 100 and n = 1000), and compare the results to those of the integral  procedure shown above. Exer cise 1.30.   The sum procedure above generates a linear recursion. The procedure can be rewritten so that the sum is performed iteratively . Show how to do this by filling in the missing expressions in the following definition: (define (sum term a next b)     (define (iter a result)       (if < ??>          < ??>         (iter < ??> <??>)))    (iter < ??> <??>))  Exer cise 1.31.      a.  The sum procedure is only the simplest of a vast number of similar abstractions that can be captured as higher -order procedures.51 Write an analogous procedure called product  that returns the product of the values of a function at points over a given range. Sho


key: chroma:document
string_value:
w how to define factorial in terms of product . Also use product  to compute approximations to π using the formula52 b.  If your product  procedure generates a recursive process, write one that generates an iterative process. If it generates an iterative process, write one that generates a recursive process. Exer cise 1.32.   a. Show that sum and product  (exercise 1.31) are both special cases of a still more general notion called accumulate  that combines a collection of terms, using some general accumulation function: (accumulate combiner null-value term a next b)   Accumulate  takes as ar guments the same term and range specifications as sum and product , together with a combiner  procedure (of two ar guments) that specifies how the current term is to be combined with the accumulation of the preceding terms and a null-value  that specifies what base value to use when the terms run out. Write accumulate  and show how sum and product  can both be defined as simple calls to accumulate . b. If your accumulate 


key: chroma:document
string_value:
 procedure generates a recursive process, write one that generates an iterative process. If it generates an iterative process, write one that generates a recursive process. Exer cise 1.33.   You can obtain an even more general version of accumulate (exercise 1.32) by introducing the notion of a filter  on the terms to be combined. That is, combine only those terms derived from values in therange that satisfy a specified condition. The resulting filtered- accumulate  abstraction takes the same ar guments as accumulate, together with an additional predicate of one ar gument that specifies the filter . Write filtered-accumulate  as a procedure. Show how to express the following using filtered-accumulate : a. the sum of the squares of the prime numbers in the interval a to b (assuming that you have a prime?  predicate already written) b. the product of all the positive integers less than n that are relatively prime to n (i.e., all positive integers i < n such that GCD(i,n) = 1). 1.3.2  Constructing Pr ocedur es U


key: chroma:document
string_value:
sing Lambda In using sum as in section 1.3.1 , it seems terribly awkward to have to define trivial procedures such as pi-term  and pi-next  just so we can use them as arguments to our higher -order procedure. Rather than define pi-next  and pi-term , it would be more convenient to have a way to directly specify “the procedure that returns its input incremented by 4” and “the procedure that returns the reciprocal of its input times its input plus 2.” We can do this by introducing the special form lambda , which creates procedures. Using lambda  we can describe what we want as (lambda (x) (+ x 4))   and (lambda (x) (/ 1.0 (* x (+ x 2))))   Then our pi-sum  procedure can be expressed without defining any auxiliary procedures as (define (pi-sum a b)     (sum (lambda (x) (/ 1.0 (* x (+ x 2))))          a          (lambda (x) (+ x 4))          b))  Again using lambda , we can write the integral  procedure without having to define the auxiliary procedure add-dx : (define (integral f a b dx)     (* (sum f            


key: chroma:document
string_value:
 (+ a (/ dx 2.0))             (lambda (x) (+ x dx))             b)        dx))   In general, lambda  is used to create procedures in the same way as define , except that no name is specified for the procedure: (lambda (< formal-parameters >) <body>)  The resulting procedure is just as much a procedure as one that is created using define . The only dif ference is that it has not been associated with any name in the environment. In fact, (define (plus4 x) (+ x 4))   is equivalent to (define plus4 (lambda (x) (+ x 4)))   We can read a lambda  expression as follows:     (lambda             (x)             (+    x     4))           ↑                 ↑               ↑    ↑    ↑    the procedure   of an argument x  that adds  x and 4  Like any expression that has a procedure as its value, a lambda  expression can be used as the operator in a combination such as ((lambda (x y z) (+ x y (square z))) 1 2 3)   12  or, more generally , in any context where we would normally use a procedure name.53Using let to cr eate loc


key: chroma:document
string_value:
al variables Another use of lambda  is in creating local variables. We often need local variables in our procedures other than those that have been bound as formal parameters. For example, suppose we wish to compute the function which we could also express as In writing a procedure to compute f, we would like to include as local variables not only x and y but also the names of intermediate quantities like a and b. One way to accomplish this is to use an auxiliary procedure to bind the local variables: (define (f x y)     (define (f-helper a b)       (+ (* x (square a))          (* y b)          (* a b)))     (f-helper (+ 1 (* x y))               (- 1 y)))   Of course, we could use a lambda  expression to specify an anonymous procedure for binding our local variables. The body of f then becomes a single call to that procedure: (define (f x y)     ((lambda (a b)        (+ (* x (square a))           (* y b)           (* a b)))      (+ 1 (* x y))      (- 1 y)))  This construct is so useful that there is a special


key: chroma:document
string_value:
 form called let to make its use more convenient. Using let, the f procedure could be written as (define (f x y)     (let ((a (+ 1 (* x y)))           (b (- 1 y)))       (+ (* x (square a))          (* y b)          (* a b))))   The general form of a let expression is (let ((< var1> <exp1>)        (< var2> <exp2>)        ⋮        (< varn> <expn>))     <body>)  which can be thought of as saying let<var1> have the value < exp1> and <var2> have the value < exp2> and ⋮ <varn> have the value < expn> in<body > The first part of the let expression is a list of name-expression pairs. When the let is evaluated, each name is associated with the value of the corresponding expression. The body of the let is evaluated with these names bound as local variables. The way this happens is that the let expression is interpreted as an alternate syntax for ((lambda (< var1> ...<varn>)      <body>)   <exp1>  ⋮   <expn>)  No new mechanism is required in the interpreter in order to provide local variables. A let expression is simply


key: chroma:document
string_value:
 syntactic sugar for the underlying lambda  application. We can see from this equivalence that the scope of a variable specified by a let expression is the body of the let. This implies that: Let allows one to bind variables as locally as possible to where they are to be used. For example, if the value of x is 5, the value of the expression (+ (let ((x 3))        (+ x (* x 10)))      x)  is 38. Here, the x in the body of the let is 3, so the value of the let expression is 33. On the other hand, the x that is the second ar gument to the outermost + is still 5. The variables' values are computed outside the let. This matters when the expressions that provide the values for the local variables depend upon variables having the same names as the local variables themselves. For example, if the value of x is 2, the expression (let ((x 3)         (y (+ x 2)))     (* x y))   will have the value 12 because, inside the body of the let, x will be 3 and y will be 4 (which is the outer x plus 2). Sometimes we can use inter


key: chroma:document
string_value:
nal definitions to get the same ef fect as with let. For example, we could have defined the procedure f above as(define (f x y)     (define a (+ 1 (* x y)))     (define b (- 1 y))     (+ (* x (square a))        (* y b)        (* a b)))   We prefer , however , to use let in situations like this and to use internal define  only for internal procedures.54 Exer cise 1.34.   Suppose we define the procedure (define (f g)     (g 2))   Then we have (f square)   4    (f (lambda (z) (* z (+ z 1))))   6  What happens if we (perversely) ask the interpreter to evaluate the combination (f f) ? Explain. 1.3.3  Pr ocedur es as General Methods We introduced compound procedures in section 1.1.4  as a mechanism for abstracting patterns of numerical operations so as to make them independent of the particular numbers involved. With higher -order procedures, such as the integral  procedure of section 1.3.1 , we began to see a more powerful kind of abstraction: procedures used to express general methods of computation, independent 


key: chroma:document
string_value:
of the particular functions involved. In this section we discuss two more elaborate examples – general methods for finding zeros and fixed points of functions – and show how these methods can be expressed directly as procedures.Finding r oots of equations by the half-interval method The half-interval method  is a simple but powerful technique for finding roots of an equation f(x) = 0, where f is a continuous function. The idea is that, if we are given points a and b such that f(a) < 0 < f(b), then f must have at least one zero between a and b. To locate a zero, let x be the average of a and b and compute f(x). If f(x) > 0, then f must have a zero between a and x. If f(x) < 0, then f must have a zero between x and b. Continuing in this way , we can identify smaller and smaller intervals on which f must have a zero. When we reach a point where the interval is small enough, the process stops. Since the interval of uncertainty is reduced by half at each step of the process, the number of steps required grows as θ


key: chroma:document
string_value:
( log( L/T)), where L is the length of the original interval and T is the error tolerance (that is, the size of the interval we will consider “small enough”). Here is a procedure that implements this strategy: (define (search f neg-point pos-point)     (let ((midpoint (average neg-point pos-point)))       (if (close-enough? neg-point pos-point)           midpoint           (let ((test-value (f midpoint)))             (cond ((positive? test-value)                    (search f neg-point midpoint))                   ((negative? test-value)                    (search f midpoint pos-point))                   (else midpoint))))))   We assume that we are initially given the function f together with points at which its values are negative and positive. We first compute the midpoint of the two given points. Next we check to see if the given interval is small enough, and if so we simply return the midpoint as our answer . Otherwise, we compute as a test value the value of f at the midpoint. If the test value is positiv


key: chroma:document
string_value:
e, then we continue the process with a new interval running from the original negative point to the midpoint. If the test value is negative, we continue with the interval from the midpoint to the positive point. Finally , there is the possibility that the test value is 0, in which case the midpoint is itself the root we are searching for .To test whether the endpoints are “close enough” we can use a procedure similar to the one used in section 1.1.7  for computing square roots:55 (define (close-enough? x y)     (< (abs (- x y)) 0.001))   Search  is awkward to use directly , because we can accidentally give it points at which f's values do not have the required sign, in which case we get a wrong answer . Instead we will use search  via the following procedure, which checks to see which of the endpoints has a negative function value and which has a positive value, and calls the search procedure accordingly . If the function has the same sign on the two given points, the half-interval method cannot be used, in w


key: chroma:document
string_value:
hich case the procedure signals an error .56 (define (half-interval-method f a b)     (let ((a-value (f a))           (b-value (f b)))       (cond ((and (negative? a-value) (positive? b-value))              (search f a b))             ((and (negative? b-value) (positive? a-value))              (search f b a))             (else              (error "Values are not of opposite sign" a b)))))   The following example uses the half-interval method to approximate π as the root between 2 and 4 of sin x = 0: (half-interval-method sin 2.0 4.0)   3.14111328125   Here is another example, using the half-interval method to search for a root of the equation x3 - 2x - 3 = 0 between 1 and 2: (half-interval-method (lambda (x) (- (* x x x) (* 2 x) 3))                         1.0                         2.0)   1.89306640625  Finding fixed points of functions A number x is called a fixed point  of a function f if x satisfies the equation f(x) = x. For some functions f we can locate a fixed point by beginning with an initial guess


key: chroma:document
string_value:
 and applying f repeatedly , until the value does not change very much. Using this idea, we can devise a procedure fixed-point  that takes as inputs a function and an initial guess and produces an approximation to a fixed point of the function. We apply the function repeatedly until we find two successive values whose difference is less than some prescribed tolerance: (define tolerance 0.00001)   (define (fixed-point f first-guess)     (define (close-enough? v1 v2)       (< (abs (- v1 v2)) tolerance))     (define (try guess)       (let ((next (f guess)))         (if (close-enough? guess next)             next             (try next))))     (try first-guess))   For example, we can use this method to approximate the fixed point of the cosine function, starting with 1 as an initial approximation:57 (fixed-point cos 1.0)   .7390822985224023   Similarly , we can find a solution to the equation y = sin y + cos y: (fixed-point (lambda (y) (+ (sin y) (cos y)))                1.0)   1.2587315962971173  The fixed-point 


key: chroma:document
string_value:
process is reminiscent of the process we used for finding square roots in section 1.1.7 . Both are based on the idea of repeatedly improving a guess until the result satisfies some criterion. In fact, we can readily formulate the square-root computation as a fixed-point search. Computing the square root of some number x requires finding a y such that y2 = x. Putting this equation into the equivalent form y = x/y, we recognize that we are looking for a fixed point of the function58 y ⟼ x/y, and we can therefore try to compute square roots as (define (sqrt x)     (fixed-point (lambda (y) (/ x y))                  1.0))   Unfortunately , this fixed-point search does not conver ge. Consider an initial guess y1. The next guess is y2 = x/y1 and the next guess is y3 = x/y2 = x/(x/y1) = y1. This results in an infinite loop in which the two guesses y1 and y2 repeat over and over , oscillating about the answer . One way to control such oscillations is to prevent the guesses from changing so much. Since the answer is al


key: chroma:document
string_value:
ways between our guess y and x/y, we can make a new guess that is not as far from y as x/y by averaging y with x/y, so that the next guess after y is (1/2)( y + x/y) instead of x/y. The process of making such a sequence of guesses is simply the process of looking for a fixed point of y ⟼ (1/2)( y + x/y): (define (sqrt x)     (fixed-point (lambda (y) (average y (/ x y)))                  1.0))   (Note that y = (1/2)( y + x/y) is a simple transformation of the equation y = x/y; to derive it, add y to both sides of the equation and divide by 2.) With this modification, the square-root procedure works. In fact, if we unravel the definitions, we can see that the sequence of approximations to the square root generated here is precisely the same as the one generated by our original square-root procedure of section 1.1.7 . This approach ofaveraging successive approximations to a solution, a technique we that we call average damping , often aids the conver gence of fixed-point searches. Exer cise 1.35.   Show that the


key: chroma:document
string_value:
 golden ratio φ (section 1.2.2 ) is a fixed point of the transformation x ⟼ 1 + 1/x, and use this fact to compute φ by means of the fixed-point  procedure. Exer cise 1.36.   Modify fixed-point  so that it prints the sequence of approximations it generates, using the newline  and display  primitives shown in exercise 1.22. Then find a solution to xx = 1000 by finding a fixed point of x ⟼ log(1000)/log(x). (Use Scheme's primitive log procedure, which computes natural logarithms.) Compare the number of steps this takes with and without average damping. (Note that you cannot start fixed- point  with a guess of 1, as this would cause division by log(1) = 0.) Exer cise 1.37.   a. An infinite continued fraction  is an expression of the form As an example, one can show that the infinite continued fraction expansion with the Ni and the Di all equal to 1 produces 1/ φ, where φ is the golden ratio (described in section 1.2.2 ). One way to approximate an infinite continued fraction is to truncate the expansion after a gi


key: chroma:document
string_value:
ven number of terms. Such a truncation – a so-called k-term finite continued fraction  – has the form Suppose that n and d are procedures of one ar gument (the term index i) that return the Ni and Di of the terms of the continued fraction. Define a procedure cont-frac  such that evaluating (cont-frac n d k)  computesthe value of the k-term finite continued fraction. Check your procedure by approximating 1/ φ using (cont-frac (lambda (i) 1.0)              (lambda (i) 1.0)              k)   for successive values of k. How lar ge must you make k in order to get an approximation that is accurate to 4 decimal places? b. If your cont-frac  procedure generates a recursive process, write one that generates an iterative process. If it generates an iterative process, write one that generates a recursive process. Exer cise 1.38.   In 1737, the Swiss mathematician Leonhard Euler published a memoir De Fractionibus Continuis , which included a continued fraction expansion for e - 2, where e is the base of the natural logar


key: chroma:document
string_value:
ithms. In this fraction, the Ni are all 1, and the Di are successively 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, .... Write a program that uses your cont-frac  procedure from exercise 1.37 to approximate e, based on Euler's expansion. Exer cise 1.39.   A continued fraction representation of the tangent function was published in 1770 by the German mathematician J.H. Lambert: where x is in radians. Define a procedure (tan-cf x k)  that computes an approximation to the tangent function based on Lambert's formula. K specifies the number of terms to compute, as in exercise 1.37. 1.3.4  Pr ocedur es as Returned V alues The above examples demonstrate how the ability to pass procedures as arguments significantly enhances the expressive power of our programming language. We can achieve even more expressive power by creating procedures whose returned values are themselves procedures.We can illustrate this idea by looking again at the fixed-point example described at the end of section 1.3.3 . We formulated a new version of the 


key: chroma:document
string_value:
square-root procedure as a fixed-point search, starting with the observation that √ x is a fixed-point of the function y ⟼ x/y. Then we used average damping to make the approximations conver ge. Average damping is a useful general technique in itself. Namely , given a function f, we consider the function whose value at x is equal to the average of x and f(x). We can express the idea of average damping by means of the following procedure: (define (average-damp f)     (lambda (x) (average x (f x))))   Average-damp  is a procedure that takes as its ar gument a procedure f and returns as its value a procedure (produced by the lambda ) that, when applied to a number x, produces the average of x and (f x) . For example, applying average-damp  to the square  procedure produces a procedure whose value at some number x is the average of x and x2. Applying this resulting procedure to 10 returns the average of 10 and 100, or 55:59 ((average-damp square) 10)   55  Using average-damp , we can reformulate the square-root p


key: chroma:document
string_value:
rocedure as follows: (define (sqrt x)     (fixed-point (average-damp (lambda (y) (/ x y)))                  1.0))   Notice how this formulation makes explicit the three ideas in the method: fixed-point search, average damping, and the function y ⟼ x/y. It is instructive to compare this formulation of the square-root method with the original version given in section 1.1.7 . Bear in mind that these procedures express the same process, and notice how much clearer the idea becomes when we express the process in terms of these abstractions. In general, there are many ways to formulate a process as a procedure. Experiencedprogrammers know how to choose procedural formulations that are particularly perspicuous, and where useful elements of the process are exposed as separate entities that can be reused in other applications. As a simple example of reuse, notice that the cube root of x is a fixed point of the function y ⟼ x/y2, so we can immediately generalize our square-root procedure to one that extracts cube roots


key: chroma:document
string_value:
:60 (define (cube-root x)     (fixed-point (average-damp (lambda (y) (/ x (square y))))                  1.0))   Newton's method When we first introduced the square-root procedure, in section 1.1.7 , we mentioned that this was a special case of Newton's method . If x ⟼ g(x) is a differentiable function, then a solution of the equation g(x) = 0 is a fixed point of the function x ⟼ f(x) where and Dg(x) is the derivative of g evaluated at x. Newton's method is the use of the fixed-point method we saw above to approximate a solution of the equation by finding a fixed point of the function f.61 For many functions g and for suf ficiently good initial guesses for x, Newton's method conver ges very rapidly to a solution of g(x) = 0.62 In order to implement Newton's method as a procedure, we must first express the idea of derivative. Note that “derivative,” like average damping, is something that transforms a function into another function. For instance, the derivative of the function x ⟼ x3 is the function x ⟼ 3x2. I


key: chroma:document
string_value:
n general, if g is a function and dx is a small number , then the derivative Dg of g is the function whose value at any number x is given (in the limit of small dx) by Thus, we can express the idea of derivative (taking dx to be, say , 0.00001) as the procedure (define (deriv g)     (lambda (x)       (/ (- (g (+ x dx)) (g x))          dx)))   along with the definition (define dx 0.00001)   Like average-damp , deriv  is a procedure that takes a procedure as argument and returns a procedure as value. For example, to approximate the derivative of x ⟼ x3 at 5 (whose exact value is 75) we can evaluate (define (cube x) (* x x x))   ((deriv cube) 5)   75.00014999664018   With the aid of deriv , we can express Newton's method as a fixed-point process: (define (newton-transform g)     (lambda (x)       (- x (/ (g x) ((deriv g) x)))))   (define (newtons-method g guess)     (fixed-point (newton-transform g) guess))   The newton-transform  procedure expresses the formula at the beginning of this section, and newtons-meth


key: chroma:document
string_value:
od  is readily defined in terms of this. It takes as arguments a procedure that computes the function for which we want to find a zero, together with an initial guess. For instance, to find the square root of x, we can use Newton's method to find a zero of the function y ⟼ y2 - x starting with an initial guess of 1.63 This provides yet another form of the square-root procedure:(define (sqrt x)     (newtons-method (lambda (y) (- (square y) x))                     1.0))   Abstractions and first-class pr ocedur es We've seen two ways to express the square-root computation as an instance of a more general method, once as a fixed-point search and once using Newton's method. Since Newton's method was itself expressed as a fixed- point process, we actually saw two ways to compute square roots as fixed points. Each method begins with a function and finds a fixed point of some transformation of the function. We can express this general idea itself as a procedure: (define (fixed-point-of-transform g transform guess)   


key: chroma:document
string_value:
  (fixed-point (transform g) guess))   This very general procedure takes as its ar guments a procedure g that computes some function, a procedure that transforms g, and an initial guess. The returned result is a fixed point of the transformed function. Using this abstraction, we can recast the first square-root computation from this section (where we look for a fixed point of the average-damped version of y ⟼ x/y) as an instance of this general method: (define (sqrt x)     (fixed-point-of-transform (lambda (y) (/ x y))                               average-damp                               1.0))   Similarly , we can express the second square-root computation from this section (an instance of Newton's method that finds a fixed point of the Newton transform of y ⟼ y2 - x) as (define (sqrt x)     (fixed-point-of-transform (lambda (y) (- (square y) x))                               newton-transform                               1.0))  We began section 1.3 with the observation that compound procedures are a cruci


key: chroma:document
string_value:
al abstraction mechanism, because they permit us to express general methods of computing as explicit elements in our programming language. Now we've seen how higher -order procedures permit us to manipulate these general methods to create further abstractions. As programmers, we should be alert to opportunities to identify the underlying abstractions in our programs and to build upon them and generalize them to create more powerful abstractions. This is not to say that one should always write programs in the most abstract way possible; expert programmers know how to choose the level of abstraction appropriate to their task. But it is important to be able to think in terms of these abstractions, so that we can be ready to apply them in new contexts. The significance of higher -order procedures is that they enable us to represent these abstractions explicitly as elements in our programming language, so that they can be handled just like other computational elements. In general, programming languages impose rest


key: chroma:document
string_value:
rictions on the ways in which computational elements can be manipulated. Elements with the fewest restrictions are said to have first-class  status. Some of the “rights and privileges” of first-class elements are:64 They may be named by variables. They may be passed as ar guments to procedures. They may be returned as the results of procedures. They may be included in data structures.65 Lisp, unlike other common programming languages, awards procedures full first-class status. This poses challenges for ef ficient implementation, but the resulting gain in expressive power is enormous.66 Exer cise 1.40.   Define a procedure cubic  that can be used together with the newtons-method  procedure in expressions of the form (newtons-method (cubic a b c) 1)   to approximate zeros of the cubic x3 + ax2 + bx + c.Exer cise 1.41.   Define a procedure double  that takes a procedure of one argument as ar gument and returns a procedure that applies the original procedure twice. For example, if inc is a procedure that adds 1 t


key: chroma:document
string_value:
o its argument, then (double inc)  should be a procedure that adds 2. What value is returned by (((double (double double)) inc) 5)   Exer cise 1.42.   Let f and g be two one-ar gument functions. The composition  f after g is defined to be the function x ⟼ f(g(x)). Define a procedure compose  that implements composition. For example, if inc is a procedure that adds 1 to its ar gument, ((compose square inc) 6)   49  Exer cise 1.43.   If f is a numerical function and n is a positive integer , then we can form the nth repeated application of f, which is defined to be the function whose value at x is f(f(...(f(x))...)). For example, if f is the function x ⟼ x + 1, then the nth repeated application of f is the function x ⟼ x + n. If f is the operation of squaring a number , then the nth repeated application of f is the function that raises its ar gument to the 2nth power . Write a procedure that takes as inputs a procedure that computes f and a positive integer n and returns the procedure that computes the nth repe


key: chroma:document
string_value:
ated application of f. Your procedure should be able to be used as follows: ((repeated square 2) 5)   625  Hint: Y ou may find it convenient to use compose  from exercise 1.42. Exer cise 1.44.   The idea of smoothing  a function is an important concept in signal processing. If f is a function and dx is some small number , then the smoothed version of f is the function whose value at a point x is the average of f(x - dx), f(x), and f(x + dx). Write a procedure smooth  that takes as input a procedure that computes f and returns a procedure that computes the smoothed f. It is sometimes valuable to repeatedly smooth a function (that is, smooth the smoothed function, and so on) to obtained the n-foldsmoothed function . Show how to generate the n-fold smoothed function of any given function using smooth  and repeated  from exercise 1.43. Exer cise 1.45.   We saw in section 1.3.3  that attempting to compute square roots by naively finding a fixed point of y ⟼ x/y does not conver ge, and that this can be fixed by ave


key: chroma:document
string_value:
rage damping. The same method works for finding cube roots as fixed points of the average-damped y ⟼ x/y2. Unfortunately , the process does not work for fourth roots – a single average damp is not enough to make a fixed-point search for y ⟼ x/y3 conver ge. On the other hand, if we average damp twice (i.e., use the average damp of the average damp of y ⟼ x/y3) the fixed-point search does conver ge. Do some experiments to determine how many average damps are required to compute nth roots as a fixed-point search based upon repeated average damping of y ⟼ x/yn-1. Use this to implement a simple procedure for computing nth roots using fixed-point , average-damp , and the repeated procedure of exercise 1.43. Assume that any arithmetic operations you need are available as primitives. Exer cise 1.46.   Several of the numerical methods described in this chapter are instances of an extremely general computational strategy known as iterative impr ovement . Iterative improvement says that, to compute something, we start w


key: chroma:document
string_value:
ith an initial guess for the answer , test if the guess is good enough, and otherwise improve the guess and continue the process using the improved guess as the new guess. Write a procedure iterative- improve  that takes two procedures as ar guments: a method for telling whether a guess is good enough and a method for improving a guess. Iterative-improve  should return as its value a procedure that takes a guess as ar gument and keeps improving the guess until it is good enough. Rewrite the sqrt  procedure of section 1.1.7  and the fixed-point  procedure of section 1.3.3  in terms of iterative-improve . 49 This series, usually written in the equivalent form (π/4) = 1 - (1/3) + (1/5) - (1/7) + ···, is due to Leibniz. We'll see how to use this as the basis for some fancy numerical tricks in section 3.5.3 . 50 Notice that we have used block structure (section 1.1.8 ) to embed the definitions of pi-next  and pi-term  within pi-sum , since these procedures are unlikely to be useful for any other purpose. We will s


key: chroma:document
string_value:
ee how to get rid of them altogether in section 1.3.2 .51 The intent of exercises 1.31-1.33 is to demonstrate the expressive power that is attained by using an appropriate abstraction to consolidate many seemingly disparate operations. However , though accumulation and filtering are elegant ideas, our hands are somewhat tied in using them at this point since we do not yet have data structures to provide suitable means of combination for these abstractions. We will return to these ideas in section 2.2.3  when we show how to use sequences  as interfaces for combining filters and accumulators to build even more powerful abstractions. We will see there how these methods really come into their own as a powerful and elegant approach to designing programs. 52 This formula was discovered by the seventeenth-century English mathematician John W allis. 53 It would be clearer and less intimidating to people learning Lisp if a name more obvious than lambda , such as make-procedure , were used. But the convention is firmly


key: chroma:document
string_value:
 entrenched. The notation is adopted from the λ calculus, a mathematical formalism introduced by the mathematical logician Alonzo Church (1941). Church developed the λ calculus to provide a rigorous foundation for studying the notions of function and function application. The λ calculus has become a basic tool for mathematical investigations of the semantics of programming languages. 54 Understanding internal definitions well enough to be sure a program means what we intend it to mean requires a more elaborate model of the evaluation process than we have presented in this chapter . The subtleties do not arise with internal definitions of procedures, however . We will return to this issue in section 4.1.6 , after we learn more about evaluation. 55 We have used 0.001 as a representative “small” number to indicate a tolerance for the acceptable error in a calculation. The appropriate tolerance for a real calculation depends upon the problem to be solved and the limitations of the computer and the algorithm. This


key: chroma:document
string_value:
 is often a very subtle consideration, requiring help from a numerical analyst or some other kind of magician. 56 This can be accomplished using error , which takes as ar guments a number of items that are printed as error messages. 57 Try this during a boring lecture: Set your calculator to radians mode and then repeatedly press the cos button until you obtain the fixed point. 58 ⟼ (pronounced “maps to”) is the mathematician's way of writing lambda . y ⟼ x/y means (lambda(y) (/ x y)) , that is, the function whose value at y is x/y. 59 Observe that this is a combination whose operator is itself a combination. Exercise 1.4 already demonstrated the ability to form such combinations, but that was only a toy example. Here we begin to see the real need for such combinations – when applying a procedure that is obtained as the value returned by a higher -order procedure. 60 See exercise 1.45 for a further generalization. 61 Elementary calculus books usually describe Newton's method in terms of the sequence of approx


key: chroma:document
string_value:
imations xn+1 = xn - g(xn)/Dg(xn). Having language for talking about processes and using the idea of fixed points simplifies the description of the method.62 Newton's method does not always conver ge to an answer , but it can be shown that in favorable cases each iteration doubles the number -of-digits accuracy of the approximation to the solution. In such cases, Newton's method will conver ge much more rapidly than the half-interval method. 63 For finding square roots, Newton's method conver ges rapidly to the correct solution from any starting point. 64 The notion of first-class status of programming-language elements is due to the British computer scientist Christopher Strachey (1916-1975). 65 We'll see examples of this after we introduce data structures in chapter 2. 66 The major implementation cost of first-class procedures is that allowing procedures to be returned as values requires reserving storage for a procedure's free variables even while the procedure is not executing. In the Scheme implementatio


key: chroma:document
string_value:
n we will study in section 4.1, these variables are stored in the procedure's environment.Chapter 2 Building Abstractions with Data We now come to the decisive step of mathematical abstraction: we for get about what the symbols stand for . ...[The mathematician] need not be idle; there are many operations which he may carry out with these symbols, without ever having to look at the things they stand for . Hermann W eyl, The Mathematical W ay of Thinking We concentrated in chapter 1 on computational processes and on the role of procedures in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic operations), how to combine procedures to form compound procedures through composition, conditionals, and the use of parameters, and how to abstract procedures by using define . We saw that a procedure can be regarded as a pattern for the local evolution of a process, and we classified, reasoned about, and performed simple algorithmic analyses of some common patterns for process


key: chroma:document
string_value:
es as embodied in procedures. We also saw that higher -order procedures enhance the power of our language by enabling us to manipulate, and thereby to reason in terms of, general methods of computation. This is much of the essence of programming. In this chapter we are going to look at more complex data. All the procedures in chapter 1 operate on simple numerical data, and simple data are not suf ficient for many of the problems we wish to address using computation. Programs are typically designed to model complex phenomena, and more often than not one must construct computational objects that have several parts in order to model real-world phenomena that have several aspects. Thus, whereas our focus in chapter 1 was on building abstractions by combining procedures to form compound procedures, we turn in this chapter to another key aspect of any programming language: the means it provides for building abstractions by combining data objects to form compound data .Why do we want compound data in a programming l


key: chroma:document
string_value:
anguage? For the same reasons that we want compound procedures: to elevate the conceptual level at which we can design our programs, to increase the modularity of our designs, and to enhance the expressive power of our language. Just as the ability to define procedures enables us to deal with processes at a higher conceptual level than that of the primitive operations of the language, the ability to construct compound data objects enables us to deal with data at a higher conceptual level than that of the primitive data objects of the language. Consider the task of designing a system to perform arithmetic with rational numbers. We could imagine an operation add-rat  that takes two rational numbers and produces their sum. In terms of simple data, a rational number can be thought of as two integers: a numerator and a denominator . Thus, we could design a program in which each rational number would be represented by two integers (a numerator and a denominator) and where add-rat  would be implemented by two proced


key: chroma:document
string_value:
ures (one producing the numerator of the sum and one producing the denominator). But this would be awkward, because we would then need to explicitly keep track of which numerators corresponded to which denominators. In a system intended to perform many operations on many rational numbers, such bookkeeping details would clutter the programs substantially , to say nothing of what they would do to our minds. It would be much better if we could “glue together” a numerator and denominator to form a pair – a compound data object  – that our programs could manipulate in a way that would be consistent with regarding a rational number as a single conceptual unit. The use of compound data also enables us to increase the modularity of our programs. If we can manipulate rational numbers directly as objects in their own right, then we can separate the part of our program that deals with rational numbers per se from the details of how rational numbers may be represented as pairs of integers. The general technique of isolat


key: chroma:document
string_value:
ing the parts of a program that deal with how data objects are represented from the parts of a program that deal with how data objects are used is a powerful design methodology called data abstraction . We will see how data abstraction makes programs much easier to design, maintain, and modify .The use of compound data leads to a real increase in the expressive power of our programming language. Consider the idea of forming a “linear combination” ax + by. We might like to write a procedure that would accept a, b, x, and y as ar guments and return the value of ax + by. This presents no difficulty if the ar guments are to be numbers, because we can readily define the procedure (define (linear-combination a b x y)     (+ (* a x) (* b y)))   But suppose we are not concerned only with numbers. Suppose we would like to express, in procedural terms, the idea that one can form linear combinations whenever addition and multiplication are defined – for rational numbers, complex numbers, polynomials, or whatever . We co


key: chroma:document
string_value:
uld express this as a procedure of the form (define (linear-combination a b x y)         (add (mul a x) (mul b y)))   where add and mul are not the primitive procedures + and * but rather more complex things that will perform the appropriate operations for whatever kinds of data we pass in as the ar guments a, b, x, and y. The key point is that the only thing linear-combination  should need to know about a, b, x, and y is that the procedures add and mul will perform the appropriate manipulations. From the perspective of the procedure linear- combination , it is irrelevant what a, b, x, and y are and even more irrelevant how they might happen to be represented in terms of more primitive data. This same example shows why it is important that our programming language provide the ability to manipulate compound objects directly: W ithout this, there is no way for a procedure such as linear- combination  to pass its ar guments along to add and mul without having to know their detailed structure.1 We begin this chap


key: chroma:document
string_value:
ter by implementing the rational-number arithmetic system mentioned above. This will form the background for our discussion of compound data and data abstraction. As with compound procedures, the main issue to be addressed is that of abstraction as a technique for coping with complexity , and we will see how data abstraction enables us to erect suitable abstraction barriers  between different parts of a program.We will see that the key to forming compound data is that a programming language should provide some kind of “glue” so that data objects can be combined to form more complex data objects. There are many possible kinds of glue. Indeed, we will discover how to form compound data using no special “data” operations at all, only procedures. This will further blur the distinction between “procedure” and “data,” which was already becoming tenuous toward the end of chapter 1. We will also explore some conventional techniques for representing sequences and trees. One key idea in dealing with compound data is th


key: chroma:document
string_value:
e notion of closur e – that the glue we use for combining data objects should allow us to combine not only primitive data objects, but compound data objects as well. Another key idea is that compound data objects can serve as conventional interfaces  for combining program modules in mix-and-match ways. We illustrate some of these ideas by presenting a simple graphics language that exploits closure. We will then augment the representational power of our language by introducing symbolic expr essions  – data whose elementary parts can be arbitrary symbols rather than only numbers. We explore various alternatives for representing sets of objects. We will find that, just as a given numerical function can be computed by many dif ferent computational processes, there are many ways in which a given data structure can be represented in terms of simpler objects, and the choice of representation can have significant impact on the time and space requirements of processes that manipulate the data. We will investigate thes


key: chroma:document
string_value:
e ideas in the context of symbolic differentiation, the representation of sets, and the encoding of information. Next we will take up the problem of working with data that may be represented dif ferently by dif ferent parts of a program. This leads to the need to implement generic operations , which must handle many dif ferent types of data. Maintaining modularity in the presence of generic operations requires more powerful abstraction barriers than can be erected with simple data abstraction alone. In particular , we introduce data-dir ected programming  as a technique that allows individual data representations to be designed in isolation and then combined additively  (i.e., without modification). To illustrate the power of this approach to system design, we close the chapter by applying what we have learned to the implementation of a package for performing symbolic arithmetic on polynomials, in whichthe coef ficients of the polynomials can be integers, rational numbers, complex numbers, and even other poly


key: chroma:document
string_value:
nomials. 1 The ability to directly manipulate procedures provides an analogous increase in the expressive power of a programming language. For example, in section 1.3.1  we introduced the sum procedure, which takes a procedure term  as an ar gument and computes the sum of the values of term  over some specified interval. In order to define sum, it is crucial that we be able to speak of a procedure such as term  as an entity in its own right, without regard for how term  might be expressed with more primitive operations. Indeed, if we did not have the notion of “a procedure,” it is doubtful that we would ever even think of the possibility of defining an operation such as sum. Moreover , insofar as performing the summation is concerned, the details of how term  may be constructed from more primitive operations are irrelevant.2.1  Introduction to Data Abstraction In section 1.1.8 , we noted that a procedure used as an element in creating a more complex procedure could be regarded not only as a collection of part


key: chroma:document
string_value:
icular operations but also as a procedural abstraction. That is, the details of how the procedure was implemented could be suppressed, and the particular procedure itself could be replaced by any other procedure with the same overall behavior . In other words, we could make an abstraction that would separate the way the procedure would be used from the details of how the procedure would be implemented in terms of more primitive procedures. The analogous notion for compound data is called data abstraction . Data abstraction is a methodology that enables us to isolate how a compound data object is used from the details of how it is constructed from more primitive data objects. The basic idea of data abstraction is to structure the programs that are to use compound data objects so that they operate on “abstract data.” That is, our programs should use data in such a way as to make no assumptions about the data that are not strictly necessary for performing the task at hand. At the same time, a “concrete” data rep


key: chroma:document
string_value:
resentation is defined independent of the programs that use the data. The interface between these two parts of our system will be a set of procedures, called selectors  and constructors , that implement the abstract data in terms of the concrete representation. To illustrate this technique, we will consider how to design a set of procedures for manipulating rational numbers. 2.1.1  Example: Arithmetic Operations for Rational Numbers Suppose we want to do arithmetic with rational numbers. We want to be able to add, subtract, multiply , and divide them and to test whether two rational numbers are equal. Let us begin by assuming that we already have a way of constructing a rational number from a numerator and a denominator . We also assume that, given a rational number , we have a way of extracting (or selecting) itsnumerator and its denominator . Let us further assume that the constructor and selectors are available as procedures: (make-rat < n> <d>) returns the rational number whose numerator is the integer <n


key: chroma:document
string_value:
> and whose denominator is the integer <d>. (numer < x>) returns the numerator of the rational number <x>. (denom < x>) returns the denominator of the rational number <x>. We are using here a powerful strategy of synthesis: wishful thinking . We haven't yet said how a rational number is represented, or how the procedures numer , denom , and make-rat  should be implemented. Even so, if we did have these three procedures, we could then add, subtract, multiply , divide, and test equality by using the following relations: We can express these rules as procedures: (define (add-rat x y)     (make-rat (+ (* (numer x) (denom y))                  (* (numer y) (denom x)))               (* (denom x) (denom y))))   (define (sub-rat x y)     (make-rat (- (* (numer x) (denom y))                  (* (numer y) (denom x)))               (* (denom x) (denom y))))  (define (mul-rat x y)     (make-rat (* (numer x) (numer y))               (* (denom x) (denom y))))   (define (div-rat x y)     (make-rat (* (numer x) (denom y))    


key: chroma:document
string_value:
           (* (denom x) (numer y))))   (define (equal-rat? x y)     (= (* (numer x) (denom y))        (* (numer y) (denom x))))   Now we have the operations on rational numbers defined in terms of the selector and constructor procedures numer , denom , and make-rat . But we haven't yet defined these. What we need is some way to glue together a numerator and a denominator to form a rational number . Pairs To enable us to implement the concrete level of our data abstraction, our language provides a compound structure called a pair, which can be constructed with the primitive procedure cons . This procedure takes two arguments and returns a compound data object that contains the two arguments as parts. Given a pair , we can extract the parts using the primitive procedures car and cdr.2 Thus, we can use cons , car, and cdr as follows: (define x (cons 1 2))     (car x)  1    (cdr x)  2  Notice that a pair is a data object that can be given a name and manipulated, just like a primitive data object. Moreover , cons 


key: chroma:document
string_value:
 can be used to form pairs whose elements are pairs, and so on:(define x (cons 1 2))     (define y (cons 3 4))     (define z (cons x y))     (car (car z))   1    (car (cdr z))   3  In section 2.2 we will see how this ability to combine pairs means that pairs can be used as general-purpose building blocks to create all sorts of complex data structures. The single compound-data primitive pair, implemented by the procedures cons , car, and cdr, is the only glue we need. Data objects constructed from pairs are called list-structur ed data. Repr esenting rational numbers Pairs of fer a natural way to complete the rational-number system. Simply represent a rational number as a pair of two integers: a numerator and a denominator . Then make-rat , numer , and denom  are readily implemented as follows:3 (define (make-rat n d) (cons n d))     (define (numer x) (car x))     (define (denom x) (cdr x))   Also, in order to display the results of our computations, we can print rational numbers by printing the numerator , a 


key: chroma:document
string_value:
slash, and the denominator:4 (define (print-rat x)     (newline)     (display (numer x))    (display "/")     (display (denom x)))   Now we can try our rational-number procedures: (define one-half (make-rat 1 2))     (print-rat one-half)   1/2    (define one-third (make-rat 1 3))   (print-rat (add-rat one-half one-third))   5/6    (print-rat (mul-rat one-half one-third))   1/6    (print-rat (add-rat one-third one-third))   6/9  As the final example shows, our rational-number implementation does not reduce rational numbers to lowest terms. We can remedy this by changing make-rat . If we have a gcd procedure like the one in section 1.2.5  that produces the greatest common divisor of two integers, we can use gcd to reduce the numerator and the denominator to lowest terms before constructing the pair: (define (make-rat n d)     (let ((g (gcd n d)))       (cons (/ n g) (/ d g))))   Now we have (print-rat (add-rat one-third one-third))   2/3  as desired. This modification was accomplished by changing the constructo


key: chroma:document
string_value:
r make-rat  without changing any of the procedures (such as add-rat and mul-rat ) that implement the actual operations. Exer cise 2.1.   Define a better version of make-rat  that handles both positive and negative ar guments. Make-rat  should normalize the sign so that if the rational number is positive, both the numerator and denominator are positive, and if the rational number is negative, only the numerator is negative. 2.1.2  Abstraction Barriers Before continuing with more examples of compound data and data abstraction, let us consider some of the issues raised by the rational-number example. We defined the rational-number operations in terms of a constructor make-rat  and selectors numer  and denom . In general, the underlying idea of data abstraction is to identify for each type of data object a basic set of operations in terms of which all manipulations of data objects of that type will be expressed, and then to use only those operations in manipulating the data. We can envision the structure of the r


key: chroma:document
string_value:
ational-number system as shown in figure 2.1. The horizontal lines represent abstraction barriers  that isolate different “levels” of the system. At each level, the barrier separates the programs (above) that use the data abstraction from the programs (below) that implement the data abstraction. Programs that use rational numbers manipulate them solely in terms of the procedures supplied “for public use” by the rational-number package: add-rat , sub-rat , mul-rat , div-rat , and equal-rat? . These, in turn, are implemented solely in terms of the constructor and selectors make-rat , numer , and denom , which themselves are implemented in terms of pairs. The details of how pairs are implemented are irrelevant to the rest of the rational-number package so long as pairs can be manipulated by the use of cons , car, and cdr. In effect, procedures at each level are the interfaces that define the abstraction barriers and connect the dif ferent levels. Figur e 2.1:   Data-abstraction barriers in the rational-number pa


key: chroma:document
string_value:
ckage.  This simple idea has many advantages. One advantage is that it makes programs much easier to maintain and to modify . Any complex data structure can be represented in a variety of ways with the primitive data structures provided by a programming language. Of course, the choice of representation influences the programs that operate on it; thus, if the representation were to be changed at some later time, all such programs might have to be modified accordingly . This task could be time-consuming and expensive in the case of lar ge programs unless the dependence on the representation were to be confined by design to a very few program modules. For example, an alternate way to address the problem of reducing rational numbers to lowest terms is to perform the reduction whenever we access the parts of a rational number , rather than when we construct it. This leads to dif ferent constructor and selector procedures: (define (make-rat n d)     (cons n d))   (define (numer x)     (let ((g (gcd (car x) (cdr x))


key: chroma:document
string_value:
))       (/ (car x) g)))   (define (denom x)     (let ((g (gcd (car x) (cdr x))))       (/ (cdr x) g)))  The dif ference between this implementation and the previous one lies in when we compute the gcd. If in our typical use of rational numbers we access the numerators and denominators of the same rational numbers many times, it would be preferable to compute the gcd when the rational numbers are constructed. If not, we may be better of f waiting until access time to compute the gcd. In any case, when we change from one representation to the other , the procedures add-rat , sub-rat , and so on do not have to be modified at all. Constraining the dependence on the representation to a few interface procedures helps us design programs as well as modify them, because it allows us to maintain the flexibility to consider alternate implementations. To continue with our simple example, suppose we are designing a rational- number package and we can't decide initially whether to perform the gcd at construction time or a


key: chroma:document
string_value:
t selection time. The data-abstraction methodology gives us a way to defer that decision without losing the ability to make progress on the rest of the system. Exer cise 2.2.   Consider the problem of representing line segments in a plane. Each segment is represented as a pair of points: a starting point and an ending point. Define a constructor make-segment  and selectors start- segment  and end-segment  that define the representation of segments in terms of points. Furthermore, a point can be represented as a pair of numbers: the x coordinate and the y coordinate. Accordingly , specify a constructor make-point  and selectors x-point  and y-point  that define this representation. Finally , using your selectors and constructors, define a procedure midpoint-segment  that takes a line segment as ar gument and returns its midpoint (the point whose coordinates are the average of the coordinates of the endpoints). To try your procedures, you'll need a way to print points: (define (print-point p)     (newline)     


key: chroma:document
string_value:
(display "(")     (display (x-point p))     (display ",")    (display (y-point p))     (display ")"))   Exer cise 2.3.   Implement a representation for rectangles in a plane. (Hint: You may want to make use of exercise 2.2.) In terms of your constructors and selectors, create procedures that compute the perimeter and the area of a given rectangle. Now implement a dif ferent representation for rectangles. Can you design your system with suitable abstraction barriers, so that the same perimeter and area procedures will work using either representation? 2.1.3  What Is Meant by Data? We began the rational-number implementation in section 2.1.1  by implementing the rational-number operations add-rat , sub-rat , and so on in terms of three unspecified procedures: make-rat , numer , and denom . At that point, we could think of the operations as being defined in terms of data objects – numerators, denominators, and rational numbers – whose behavior was specified by the latter three procedures. But exactly what is mea


key: chroma:document
string_value:
nt by data? It is not enough to say “whatever is implemented by the given selectors and constructors.” Clearly , not every arbitrary set of three procedures can serve as an appropriate basis for the rational-number implementation. We need to guarantee that, if we construct a rational number x from a pair of integers n and d, then extracting the numer  and the denom  of x and dividing them should yield the same result as dividing n by d. In other words, make-rat , numer , and denom  must satisfy the condition that, for any integer n and any non-zero integer d, if x is (make-rat n d ), then In fact, this is the only condition make-rat , numer , and denom  must fulfill in order to form a suitable basis for a rational-number representation. In general, we can think of data as defined by some collection of selectors and constructors, together with specified conditions that these procedures must fulfill in order to be a valid representation.5This point of view can serve to define not only “high-level” data objects,


key: chroma:document
string_value:
 such as rational numbers, but lower -level objects as well. Consider the notion of a pair , which we used in order to define our rational numbers. We never actually said what a pair was, only that the language supplied procedures cons , car, and cdr for operating on pairs. But the only thing we need to know about these three operations is that if we glue two objects together using cons  we can retrieve the objects using car and cdr. That is, the operations satisfy the condition that, for any objects x and y, if z is (cons x y)  then (car z)  is x and (cdr z)  is y. Indeed, we mentioned that these three procedures are included as primitives in our language. However , any triple of procedures that satisfies the above condition can be used as the basis for implementing pairs. This point is illustrated strikingly by the fact that we could implement cons , car, and cdr without using any data structures at all but only using procedures. Here are the definitions: (define (cons x y)     (define (dispatch m)       (c


key: chroma:document
string_value:
ond ((= m 0) x)             ((= m 1) y)             (else (error "Argument not 0 or 1 -- CONS" m))))     dispatch)     (define (car z) (z 0))     (define (cdr z) (z 1))   This use of procedures corresponds to nothing like our intuitive notion of what data should be. Nevertheless, all we need to do to show that this is a valid way to represent pairs is to verify that these procedures satisfy the condition given above. The subtle point to notice is that the value returned by (cons x y)  is a procedure – namely the internally defined procedure dispatch , which takes one ar gument and returns either x or y depending on whether the ar gument is 0 or 1. Correspondingly , (car z)  is defined to apply z to 0. Hence, if z is the procedure formed by (cons x y) , then z applied to 0 will yield x. Thus, we have shown that (car (cons x y))  yields x, as desired. Similarly , (cdr(cons x y))  applies the procedure returned by (cons x y)  to 1, which returns y. Therefore, this procedural implementation of pairs is a valid im


key: chroma:document
string_value:
plementation, and if we access pairs using only cons , car, and cdr we cannot distinguish this implementation from one that uses “real” data structures. The point of exhibiting the procedural representation of pairs is not that our language works this way (Scheme, and Lisp systems in general, implement pairs directly , for ef ficiency reasons) but that it could work this way . The procedural representation, although obscure, is a perfectly adequate way to represent pairs, since it fulfills the only conditions that pairs need to fulfill. This example also demonstrates that the ability to manipulate procedures as objects automatically provides the ability to represent compound data. This may seem a curiosity now , but procedural representations of data will play a central role in our programming repertoire. This style of programming is often called message passing , and we will be using it as a basic tool in chapter 3 when we address the issues of modeling and simulation. Exer cise 2.4.   Here is an alternative


key: chroma:document
string_value:
 procedural representation of pairs. For this representation, verify that (car (cons x y))  yields x for any objects x and y. (define (cons x y)     (lambda (m) (m x y)))     (define (car z)     (z (lambda (p q) p)))   What is the corresponding definition of cdr? (Hint: T o verify that this works, make use of the substitution model of section 1.1.5 .) Exer cise 2.5.   Show that we can represent pairs of nonnegative integers using only numbers and arithmetic operations if we represent the pair a and b as the integer that is the product 2a 3b. Give the corresponding definitions of the procedures cons , car, and cdr. Exer cise 2.6.   In case representing pairs as procedures wasn't mind- boggling enough, consider that, in a language that can manipulateprocedures, we can get by without numbers (at least insofar as nonnegative integers are concerned) by implementing 0 and the operation of adding 1 as (define zero (lambda (f) (lambda (x) x)))     (define (add-1 n)     (lambda (f) (lambda (x) (f ((n f) x)))))   This 


key: chroma:document
string_value:
representation is known as Chur ch numerals , after its inventor , Alonzo Church, the logician who invented the λ calculus. Define one and two directly (not in terms of zero  and add-1 ). (Hint: Use substitution to evaluate (add-1 zero) ). Give a direct definition of the addition procedure + (not in terms of repeated application of add-1 ). 2.1.4  Extended Exer cise: Interval Arithmetic Alyssa P . Hacker is designing a system to help people solve engineering problems. One feature she wants to provide in her system is the ability to manipulate inexact quantities (such as measured parameters of physical devices) with known precision, so that when computations are done with such approximate quantities the results will be numbers of known precision. Electrical engineers will be using Alyssa's system to compute electrical quantities. It is sometimes necessary for them to compute the value of a parallel equivalent resistance Rp of two resistors R1 and R2 using the formula Resistance values are usually known only up


key: chroma:document
string_value:
 to some tolerance guaranteed by the manufacturer of the resistor . For example, if you buy a resistor labeled “6.8 ohms with 10% tolerance” you can only be sure that the resistor has a resistance between 6.8 - 0.68 = 6.12 and 6.8 + 0.68 = 7.48 ohms. Thus, if you have a 6.8-ohm 10% resistor in parallel with a 4.7-ohm 5% resistor , the resistance of the combination can range from about 2.58ohms (if the two resistors are at the lower bounds) to about 2.97 ohms (if the two resistors are at the upper bounds). Alyssa's idea is to implement “interval arithmetic” as a set of arithmetic operations for combining “intervals” (objects that represent the range of possible values of an inexact quantity). The result of adding, subtracting, multiplying, or dividing two intervals is itself an interval, representing the range of the result. Alyssa postulates the existence of an abstract object called an “interval” that has two endpoints: a lower bound and an upper bound. She also presumes that, given the endpoints of an inter


key: chroma:document
string_value:
val, she can construct the interval using the data constructor make-interval . Alyssa first writes a procedure for adding two intervals. She reasons that the minimum value the sum could be is the sum of the two lower bounds and the maximum value it could be is the sum of the two upper bounds: (define (add-interval x y)     (make-interval (+ (lower-bound x) (lower-bound y))                    (+ (upper-bound x) (upper-bound y))))   Alyssa also works out the product of two intervals by finding the minimum and the maximum of the products of the bounds and using them as the bounds of the resulting interval. (Min and max are primitives that find the minimum or maximum of any number of ar guments.) (define (mul-interval x y)     (let ((p1 (* (lower-bound x) (lower-bound y)))           (p2 (* (lower-bound x) (upper-bound y)))           (p3 (* (upper-bound x) (lower-bound y)))           (p4 (* (upper-bound x) (upper-bound y))))       (make-interval (min p1 p2 p3 p4)                      (max p1 p2 p3 p4))))   To divi


key: chroma:document
string_value:
de two intervals, Alyssa multiplies the first by the reciprocal of the second. Note that the bounds of the reciprocal interval are the reciprocal of the upper bound and the reciprocal of the lower bound, in that order .(define (div-interval x y)     (mul-interval x                   (make-interval (/ 1.0 (upper-bound y))                                  (/ 1.0 (lower-bound y)))))   Exer cise 2.7.   Alyssa's program is incomplete because she has not specified the implementation of the interval abstraction. Here is a definition of the interval constructor: (define (make-interval a b) (cons a b))   Define selectors upper-bound  and lower-bound  to complete the implementation. Exer cise 2.8.   Using reasoning analogous to Alyssa's, describe how the difference of two intervals may be computed. Define a corresponding subtraction procedure, called sub-interval . Exer cise 2.9.   The width  of an interval is half of the dif ference between its upper and lower bounds. The width is a measure of the uncertainty of the n


key: chroma:document
string_value:
umber specified by the interval. For some arithmetic operations the width of the result of combining two intervals is a function only of the widths of the ar gument intervals, whereas for others the width of the combination is not a function of the widths of the ar gument intervals. Show that the width of the sum (or dif ference) of two intervals is a function only of the widths of the intervals being added (or subtracted). Give examples to show that this is not true for multiplication or division. Exer cise 2.10.   Ben Bitdiddle, an expert systems programmer , looks over Alyssa's shoulder and comments that it is not clear what it means to divide by an interval that spans zero. Modify Alyssa's code to check for this condition and to signal an error if it occurs. Exer cise 2.1 1.  In passing, Ben also cryptically comments: “By testing the signs of the endpoints of the intervals, it is possible to break mul-interval into nine cases, only one of which requires more than two multiplications.” Rewrite this procedu


key: chroma:document
string_value:
re using Ben's suggestion.After debugging her program, Alyssa shows it to a potential user , who complains that her program solves the wrong problem. He wants a program that can deal with numbers represented as a center value and an additive tolerance; for example, he wants to work with intervals such as 3.5± 0.15 rather than [3.35, 3.65]. Alyssa returns to her desk and fixes this problem by supplying an alternate constructor and alternate selectors: (define (make-center-width c w)     (make-interval (- c w) (+ c w)))   (define (center i)     (/ (+ (lower-bound i) (upper-bound i)) 2))   (define (width i)     (/ (- (upper-bound i) (lower-bound i)) 2))   Unfortunately , most of Alyssa's users are engineers. Real engineering situations usually involve measurements with only a small uncertainty , measured as the ratio of the width of the interval to the midpoint of the interval. Engineers usually specify percentage tolerances on the parameters of devices, as in the resistor specifications given earlier . Exer cis


key: chroma:document
string_value:
e 2.12.   Define a constructor make-center-percent  that takes a center and a percentage tolerance and produces the desired interval. You must also define a selector percent  that produces the percentage tolerance for a given interval. The center  selector is the same as the one shown above. Exer cise 2.13.   Show that under the assumption of small percentage tolerances there is a simple formula for the approximate percentage tolerance of the product of two intervals in terms of the tolerances of the factors. You may simplify the problem by assuming that all numbers are positive. After considerable work, Alyssa P . Hacker delivers her finished system. Several years later , after she has for gotten all about it, she gets a frenzied call from an irate user , Lem E. T weakit. It seems that Lem has noticed that the formula for parallel resistors can be written in two algebraically equivalent ways:and He has written the following two programs, each of which computes the parallel-resistors formula dif ferently: (de


key: chroma:document
string_value:
fine (par1 r1 r2)     (div-interval (mul-interval r1 r2)                   (add-interval r1 r2)))   (define (par2 r1 r2)     (let ((one (make-interval 1 1)))       (div-interval one                     (add-interval (div-interval one r1)                                   (div-interval one r2)))))   Lem complains that Alyssa's program gives dif ferent answers for the two ways of computing. This is a serious complaint. Exer cise 2.14.   Demonstrate that Lem is right. Investigate the behavior of the system on a variety of arithmetic expressions. Make some intervals A and B, and use them in computing the expressions A/A and A/B. You will get the most insight by using intervals whose width is a small percentage of the center value. Examine the results of the computation in center -percent form (see exercise 2.12). Exer cise 2.15.   Eva Lu Ator , another user , has also noticed the dif ferent intervals computed by dif ferent but algebraically equivalent expressions. She says that a formula to compute with intervals


key: chroma:document
string_value:
 using Alyssa's system will produce tighter error bounds if it can be written in such a form that no variable that represents an uncertain number is repeated. Thus, she says, par2  is a “better” program for parallel resistances than par1 . Is she right? Why?Exer cise 2.16.   Explain, in general, why equivalent algebraic expressions may lead to dif ferent answers. Can you devise an interval-arithmetic package that does not have this shortcoming, or is this task impossible? (Warning: This problem is very dif ficult.) 2 The name cons  stands for “construct.” The names car and cdr derive from the original implementation of Lisp on the IBM 704. That machine had an addressing scheme that allowed one to reference the “address” and “decrement” parts of a memory location. Car stands for “Contents of Address part of Register” and cdr (pronounced “could-er”) stands for “Contents of Decrement part of Register .” 3 Another way to define the selectors and constructor is (define make-rat cons)   (define numer car)   (define


key: chroma:document
string_value:
 denom cdr)   The first definition associates the name make-rat  with the value of the expression cons , which is the primitive procedure that constructs pairs. Thus make-rat  and cons  are names for the same primitive constructor . Defining selectors and constructors in this way is ef ficient: Instead of make-rat  calling  cons , make- rat is cons , so there is only one procedure called, not two, when make-rat  is called. On the other hand, doing this defeats debugging aids that trace procedure calls or put breakpoints on procedure calls: You may want to watch make-rat  being called, but you certainly don't want to watch every call to cons . We have chosen not to use this style of definition in this book. 4 Display  is the Scheme primitive for printing data. The Scheme primitive newline  starts a new line for printing. Neither of these procedures returns a useful value, so in the uses of print-rat  below , we show only what print-rat  prints, not what the interpreter prints as the value returned by print- ra


key: chroma:document
string_value:
t. 5 Surprisingly , this idea is very dif ficult to formulate rigorously . There are two approaches to giving such a formulation. One, pioneered by C. A. R. Hoare (1972), is known as the method of abstract models . It formalizes the “procedures plus conditions” specification as outlined in the rational- number example above. Note that the condition on the rational-number representation was stated in terms of facts about integers (equality and division). In general, abstract models define new kinds of data objects in terms of previously defined types of data objects. Assertions about data objects can therefore be checked by reducing them to assertions about previously defined data objects. Another approach, introduced by Zilles at MIT , by Goguen, Thatcher , Wagner , and Wright at IBM (see Thatcher , Wagner , and W right 1978), and by Guttag at T oronto (see Guttag 1977), is called algebraic specification . It regards the “procedures” as elements of an abstract algebraic system whose behavior is specified by a


key: chroma:document
string_value:
xioms that correspond to our “conditions,” and uses the techniques of abstract algebra to check assertions about data objects. Both methods are surveyed in the paper by Liskov and Zilles (1975).2.2  Hierarchical Data and the Closure Property As we have seen, pairs provide a primitive “glue” that we can use to construct compound data objects. Figure 2.2 shows a standard way to visualize a pair – in this case, the pair formed by (cons 1 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer  to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr. We have already seen that cons  can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises 2.2 and 2.3.) As a consequence, pairs provid


key: chroma:document
string_value:
e a universal building block from which we can construct all sorts of data structures. Figure 2.3 shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Figur e 2.2:   Box-and-pointer representation of (cons 1 2) . Figur e 2.3:   Two ways to combine 1, 2, 3, and 4 using pairs. The ability to create pairs whose elements are pairs is the essence of list structure's importance as a representational tool. We refer to this ability as the closur e property  of cons . In general, an operation for combining data objects satisfies the closure property if the results of combining things withthat operation can themselves be combined using the same operation.6 Closure is the key to power in any means of combination because it permits us to create hierar chical  structures – structures made up of parts, which themselves are made up of parts, and so on. From the outset of chapter 1, we've made essential use of closure in dealing with procedures, because all but the very simplest programs rely on the fact that t


key: chroma:document
string_value:
he elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way .7 2.2.1  Repr esenting Sequences Figur e 2.4:   The sequence 1, 2, 3, 4 represented as a chain of pairs. One of the useful structures we can build with pairs is a sequence  – an ordered collection of data objects. There are, of course, many ways to represent sequences in terms of pairs. One particularly straightforward representation is illustrated in figure 2.4, where the sequence 1, 2, 3, 4 is represented as a chain of pairs. The car of each pair is the corresponding item in the chain, and the cdr of the pair is the next pair in the chain. The cdr of the final pair signals the end of the sequence by pointing to a distinguished value that is not a pair , represented in box-and-pointer diagrams as a diagonal lin


key: chroma:document
string_value:
e and in programs as the value of the variable nil. The entire sequence is constructed by nested cons  operations: (cons 1        (cons 2               (cons 3                     (cons 4 nil))))  Such a sequence of pairs, formed by nested cons es, is called a list, and Scheme provides a primitive called list  to help in constructing lists.8 The above sequence could be produced by (list 1 2 3 4) . In general, (list <a1> <a2> ... <an>)  is equivalent to (cons <a1> (cons < a2> (cons ... (cons < an> nil) ...)))  Lisp systems conventionally print lists by printing the sequence of elements, enclosed in parentheses. Thus, the data object in figure 2.4 is printed as (1 2 3 4) : (define one-through-four (list 1 2 3 4))     one-through-four   (1 2 3 4)   Be careful not to confuse the expression (list 1 2 3 4)  with the list (1 2 3 4) , which is the result obtained when the expression is evaluated. Attempting to evaluate the expression (1 2 3 4)  will signal an error when the interpreter tries to apply the procedure 1 


key: chroma:document
string_value:
to ar guments 2, 3, and 4. We can think of car as selecting the first item in the list, and of cdr as selecting the sublist consisting of all but the first item. Nested applications of car and cdr can be used to extract the second, third, and subsequent items in the list.9 The constructor cons  makes a list like the original one, but with an additional item at the beginning. (car one-through-four)   1    (cdr one-through-four)   (2 3 4)  (car (cdr one-through-four))   2   (cons 10 one-through-four)   (10 1 2 3 4)     (cons 5 one-through-four)   (5 1 2 3 4)   The value of nil, used to terminate the chain of pairs, can be thought of as a sequence of no elements, the empty list . The word nil is a contraction of the Latin word nihil, which means “nothing.”10 List operations The use of pairs to represent sequences of elements as lists is accompanied by conventional programming techniques for manipulating lists by successively “cdring down” the lists. For example, the procedure list-ref takes as ar guments a list 


key: chroma:document
string_value:
and a number n and returns the nth item of the list. It is customary to number the elements of the list beginning with 0. The method for computing list-ref  is the following: For n = 0, list-ref  should return the car of the list. Otherwise, list-ref  should return the (n - 1)st item of the cdr of the list. (define (list-ref items n)     (if (= n 0)         (car items)         (list-ref (cdr items) (- n 1))))   (define squares (list 1 4 9 16 25))     (list-ref squares 3)   16  Often we cdr down the whole list. To aid in this, Scheme includes a primitive predicate null? , which tests whether its ar gument is the empty list. The procedure length , which returns the number of items in a list, illustrates this typical pattern of use:(define (length items)     (if (null? items)         0        (+ 1 (length (cdr items)))))   (define odds (list 1 3 5 7))     (length odds)   4  The length  procedure implements a simple recursive plan. The reduction step is: The length  of any list is 1 plus the length  of the cdr of


key: chroma:document
string_value:
 the list. This is applied successively until we reach the base case: The length  of the empty list is 0. We could also compute length  in an iterative style: (define (length items)     (define (length-iter a count)       (if (null? a)           count           (length-iter (cdr a) (+ 1 count))))     (length-iter items 0))   Another conventional programming technique is to “cons  up” an answer list while cdring down a list, as in the procedure append , which takes two lists as arguments and combines their elements to make a new list: (append squares odds)   (1 4 9 16 25 1 3 5 7)     (append odds squares)   (1 3 5 7 1 4 9 16 25)  Append  is also implemented using a recursive plan. To append  lists list1 and list2 , do the following: If list1  is the empty list, then the result is just list2 . Otherwise, append  the cdr of list1  and list2 , and cons  the car of list1  onto the result: (define (append list1 list2)     (if (null? list1)         list2         (cons (car list1) (append (cdr list1) list2))))   Exer


key: chroma:document
string_value:
 cise 2.17.   Define a procedure last-pair  that returns the list that contains only the last element of a given (nonempty) list: (last-pair (list 23 72 149 34))   (34)  Exer cise 2.18.   Define a procedure reverse  that takes a list as ar gument and returns a list of the same elements in reverse order: (reverse (list 1 4 9 16 25))   (25 16 9 4 1)   Exer cise 2.19.   Consider the change-counting program of section 1.2.2 . It would be nice to be able to easily change the currency used by the program, so that we could compute the number of ways to change a British pound, for example. As the program is written, the knowledge of the currency is distributed partly into the procedure first-denomination  and partly into the procedure count-change  (which knows that there are five kinds of U.S. coins). It would be nicer to be able to supply a list of coins to be used for making change. We want to rewrite the procedure cc so that its second ar gument is a list of the values of the coins to use rather than an integer s


key: chroma:document
string_value:
pecifying which coins to use. We could then have lists that defined each kind of currency:(define us-coins (list 50 25 10 5 1))   (define uk-coins (list 100 50 20 10 5 2 1 0.5))   We could then call cc as follows: (cc 100 us-coins)   292  To do this will require changing the program cc somewhat. It will still have the same form, but it will access its second ar gument dif ferently , as follows: (define (cc amount coin-values)     (cond ((= amount 0) 1)           ((or (< amount 0) (no-more? coin-values)) 0)           (else            (+ (cc amount                   (except-first-denomination coin-values))               (cc (- amount                      (first-denomination coin-values))                   coin-values)))))   Define the procedures first-denomination , except-first-denomination , and no-more?  in terms of primitive operations on list structures. Does the order of the list coin-values  affect the answer produced by cc? Why or why not? Exer cise 2.20.   The procedures +, *, and list  take arbitrary 


key: chroma:document
string_value:
numbers of arguments. One way to define such procedures is to use define  with dotted- tail notation . In a procedure definition, a parameter list that has a dot before the last parameter name indicates that, when the procedure is called, the initial parameters (if any) will have as values the initial ar guments, as usual, but the final parameter's value will be a list of any remaining ar guments. For instance, given the definition (define (f x y . z) <body>)  the procedure f can be called with two or more ar guments. If we evaluate(f 1 2 3 4 5 6)   then in the body of f, x will be 1, y will be 2, and z will be the list (3 4 5 6). Given the definition (define (g . w) <body>)  the procedure g can be called with zero or more ar guments. If we evaluate (g 1 2 3 4 5 6)   then in the body of g, w will be the list (1 2 3 4 5 6) .11 Use this notation to write a procedure same-parity  that takes one or more integers and returns a list of all the ar guments that have the same even-odd parity as the first ar gument. Fo


key: chroma:document
string_value:
r example, (same-parity 1 2 3 4 5 6 7)   (1 3 5 7)     (same-parity 2 3 4 5 6 7)   (2 4 6)  Mapping over lists One extremely useful operation is to apply some transformation to each element in a list and generate the list of results. For instance, the following procedure scales each number in a list by a given factor: (define (scale-list items factor)     (if (null? items)         nil         (cons (* (car items) factor)               (scale-list (cdr items) factor))))   (scale-list (list 1 2 3 4 5) 10)   (10 20 30 40 50)  We can abstract this general idea and capture it as a common pattern expressed as a higher -order procedure, just as in section 1.3. The higher - order procedure here is called map. Map takes as ar guments a procedure of one ar gument and a list, and returns a list of the results produced by applying the procedure to each element in the list:12 (define (map proc items)     (if (null? items)         nil         (cons (proc (car items))               (map proc (cdr items)))))   (map abs (list


key: chroma:document
string_value:
 -10 2.5 -11.6 17))   (10 2.5 11.6 17)   (map (lambda (x) (* x x))        (list 1 2 3 4))   (1 4 9 16)   Now we can give a new definition of scale-list  in terms of map: (define (scale-list items factor)     (map (lambda (x) (* x factor))          items))   Map is an important construct, not only because it captures a common pattern, but because it establishes a higher level of abstraction in dealing with lists. In the original definition of scale-list , the recursive structure of the program draws attention to the element-by-element processing of the list. Defining scale-list  in terms of map suppresses that level of detail and emphasizes that scaling transforms a list of elements to a list of results. The difference between the two definitions is not that the computer is performing a dif ferent process (it isn't) but that we think about the process differently . In effect, map helps establish an abstraction barrier that isolates the implementation of procedures that transform lists from the details of how t


key: chroma:document
string_value:
he elements of the list are extracted and combined. Like the barriers shown in figure 2.1, this abstraction gives us the flexibility to change the low-level details of how sequences are implemented, while preserving the conceptual framework of operations that transform sequences to sequences.Section 2.2.3  expands on this use of sequences as a framework for organizing programs. Exer cise 2.21.   The procedure square-list  takes a list of numbers as argument and returns a list of the squares of those numbers. (square-list (list 1 2 3 4))   (1 4 9 16)   Here are two dif ferent definitions of square-list . Complete both of them by filling in the missing expressions: (define (square-list items)     (if (null? items)         nil         (cons < ??> <??>)))  (define (square-list items)     (map < ??> <??>))  Exer cise 2.22.   Louis Reasoner tries to rewrite the first square-list procedure of exercise 2.21 so that it evolves an iterative process: (define (square-list items)     (define (iter things answer)       (if


key: chroma:document
string_value:
 (null? things)           answer           (iter (cdr things)                 (cons (square (car things))                       answer))))     (iter items nil))   Unfortunately , defining square-list  this way produces the answer list in the reverse order of the one desired. Why? Louis then tries to fix his bug by interchanging the ar guments to cons : (define (square-list items)     (define (iter things answer)      (if (null? things)           answer           (iter (cdr things)                 (cons answer                       (square (car things))))))     (iter items nil))   This doesn't work either . Explain. Exer cise 2.23.   The procedure for-each  is similar to map. It takes as arguments a procedure and a list of elements. However , rather than forming a list of the results, for-each  just applies the procedure to each of the elements in turn, from left to right. The values returned by applying the procedure to the elements are not used at all – for-each  is used with procedures that perform an actio


key: chroma:document
string_value:
n, such as printing. For example, (for-each (lambda (x) (newline) (display x))             (list 57 321 88))   57  321  88  The value returned by the call to for-each  (not illustrated above) can be something arbitrary , such as true. Give an implementation of for-each . 2.2.2  Hierar chical Structur es The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4)  constructed by (cons (list 1 2) (list 3 4))   as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter . Figure 2.5 shows the representation of this structure in terms of pairs. Figur e 2.5:   Structure formed by (cons (list 1 2) (list 3 4)) .Another way to think of sequences whose elements are sequences is as trees. The elements of the sequence are the branches of the tree, and elements that are themselves sequences are su


key: chroma:document
string_value:
btrees. Figure 2.6 shows the structure in figure 2.5 viewed as a tree. Figur e 2.6:   The list structure in figure 2.5 viewed as a tree. Recursion is a natural tool for dealing with tree structures, since we can often reduce operations on trees to operations on their branches, which reduce in turn to operations on the branches of the branches, and so on, until we reach the leaves of the tree. As an example, compare the length procedure of section 2.2.1  with the count-leaves  procedure, which returns the total number of leaves of a tree: (define x (cons (list 1 2) (list 3 4)))     (length x)   3  (count-leaves x)   4    (list x x)   (((1 2) 3 4) ((1 2) 3 4))    (length (list x x))   2    (count-leaves (list x x))   8  To implement count-leaves , recall the recursive plan for computing length : Length  of a list x is 1 plus length  of the cdr of x. Length  of the empty list is 0. Count-leaves  is similar . The value for the empty list is the same: Count-leaves  of the empty list is 0. But in the reduction step


key: chroma:document
string_value:
, where we strip of f the car of the list, we must take into account that the car may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves  of a tree x is count-leaves  of the car of x plus count- leaves  of the cdr of x. Finally , by taking cars we reach actual leaves, so we need another base case: Count-leaves  of a leaf is 1. To aid in writing recursive procedures on trees, Scheme provides the primitive predicate pair? , which tests whether its ar gument is a pair . Here is the complete procedure:13 (define (count-leaves x)     (cond ((null? x) 0)            ((not (pair? x)) 1)          (else (+ (count-leaves (car x))                    (count-leaves (cdr x))))))   Exer cise 2.24.   Suppose we evaluate the expression (list 1 (list 2 (list 3 4))) . Give the result printed by the interpreter , the corresponding box-and-pointer structure, and the interpretation of this as a tree (as in figure 2.6). Exer cise 2.25.   Give combinations of cars and cdrs that will p


key: chroma:document
string_value:
ick 7 from each of the following lists: (1 3 (5 7) 9)     ((7))    (1 (2 (3 (4 (5 (6 7))))))   Exer cise 2.26.   Suppose we define x and y to be two lists: (define x (list 1 2 3))   (define y (list 4 5 6))   What result is printed by the interpreter in response to evaluating each of the following expressions: (append x y)     (cons x y)     (list x y)   Exer cise 2.27.   Modify your reverse  procedure of exercise 2.18 to produce a deep-reverse  procedure that takes a list as ar gument and returns as its value the list with its elements reversed and with all sublists deep-reversed as well. For example, (define x (list (list 1 2) (list 3 4)))    x  ((1 2) (3 4))     (reverse x)   ((3 4) (1 2))     (deep-reverse x)   ((4 3) (2 1))   Exer cise 2.28.   Write a procedure fringe  that takes as ar gument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order . For example, (define x (list (list 1 2) (list 3 4)))     (fringe x)   (1 2 3 4)     (f


key: chroma:document
string_value:
ringe (list x x))   (1 2 3 4 1 2 3 4)   Exer cise 2.29.   A binary mobile consists of two branches, a left branch and a right branch. Each branch is a rod of a certain length, from which hangs either a weight or another binary mobile. We can represent a binary mobile using compound data by constructing it from two branches (for example, using list ): (define (make-mobile left right)     (list left right))   A branch is constructed from a length  (which must be a number) together with a structure , which may be either a number (representing a simple weight) or another mobile: (define (make-branch length structure)     (list length structure))  a.  Write the corresponding selectors left-branch  and right-branch , which return the branches of a mobile, and branch-length  and branch- structure , which return the components of a branch. b.  Using your selectors, define a procedure total-weight  that returns the total weight of a mobile. c.  A mobile is said to be balanced  if the torque applied by its top-left bra


key: chroma:document
string_value:
nch is equal to that applied by its top-right branch (that is, if the length of the left rod multiplied by the weight hanging from that rod is equal to the corresponding product for the right side) and if each of the submobiles hanging of f its branches is balanced. Design a predicate that tests whether a binary mobile is balanced. d.  Suppose we change the representation of mobiles so that the constructors are (define (make-mobile left right)     (cons left right))   (define (make-branch length structure)     (cons length structure))   How much do you need to change your programs to convert to the new representation? Mapping over tr ees Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree  procedure, analogous to scale-list  of section 2.2.1 , takes as ar guments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the


key: chroma:document
string_value:
 factor . The recursive plan for scale-tree  is similar to the one for count-leaves : (define (scale-tree tree factor)     (cond ((null? tree) nil)           ((not (pair? tree)) (* tree factor))          (else (cons (scale-tree (car tree) factor)                       (scale-tree (cdr tree) factor)))))   (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7))               10)   (10 (20 (30 40) 50) (60 70))   Another way to implement scale-tree  is to regard the tree as a sequence of sub-trees and use map. We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: (define (scale-tree tree factor)     (map (lambda (sub-tree)            (if (pair? sub-tree)                (scale-tree sub-tree factor)                (* sub-tree factor)))          tree))   Many tree operations can be implemented by similar combinations of sequence operations and recursion. Exer cise 2.30.   Define a procedure square-tree  ana


key: chroma:document
string_value:
logous to the square- list  procedure of exercise 2.21. That is, square-list  should behave as follows: (square-tree    (list 1          (list 2 (list 3 4) 5)          (list 6 7)))   (1 (4 (9 16) 25) (36 49))   Define square-tree  both directly (i.e., without using any higher -order procedures) and also by using map and recursion. Exer cise 2.31.   Abstract your answer to exercise 2.30 to produce a procedure tree-map  with the property that square-tree  could be defined as (define (square-tree tree) (tree-map square tree))  Exer cise 2.32.   We can represent a set as a list of distinct elements, and we can represent the set of all subsets of the set as a list of lists. For example, if the set is (1 2 3) , then the set of all subsets is (() (3) (2) (2 3) (1) (1 3) (1 2) (1 2 3)) . Complete the following definition of a procedure that generates the set of subsets of a set and give a clear explanation of why it works: (define (subsets s)     (if (null? s)         (list nil)         (let ((rest (subsets (cdr s)))


key: chroma:document
string_value:
)           (append rest (map < ??> rest)))))   2.2.3  Sequences as Conventional Interfaces In working with compound data, we've stressed how data abstraction permits us to design programs without becoming enmeshed in the details of data representations, and how abstraction preserves for us the flexibility to experiment with alternative representations. In this section, we introduce another powerful design principle for working with data structures – the use of conventional interfaces . In section 1.3 we saw how program abstractions, implemented as higher - order procedures, can capture common patterns in programs that deal with numerical data. Our ability to formulate analogous operations for working with compound data depends crucially on the style in which we manipulate our data structures. Consider , for example, the following procedure, analogous to the count-leaves  procedure of section 2.2.2 , which takes a tree as ar gument and computes the sum of the squares of the leaves that are odd: (define (sum-o


key: chroma:document
string_value:
dd-squares tree)     (cond ((null? tree) 0)            ((not (pair? tree))            (if (odd? tree) (square tree) 0))           (else (+ (sum-odd-squares (car tree))                    (sum-odd-squares (cdr tree))))))  On the surface, this procedure is very dif ferent from the following one, which constructs a list of all the even Fibonacci numbers Fib(k), where k is less than or equal to a given integer n: (define (even-fibs n)     (define (next k)       (if (> k n)           nil           (let ((f (fib k)))             (if (even? f)                 (cons f (next (+ k 1)))                 (next (+ k 1))))))     (next 0))   Despite the fact that these two procedures are structurally very dif ferent, a more abstract description of the two computations reveals a great deal of similarity . The first program enumerates the leaves of a tree; filters them, selecting the odd ones; squares each of the selected ones; and accumulates the results using +, starting with 0. The second program enumerates the integers fro


key: chroma:document
string_value:
m 0 to n; computes the Fibonacci number for each integer; filters them, selecting the even ones; and accumulates the results using cons , starting with the empty list. A signal-processing engineer would find it natural to conceptualize these processes in terms of signals flowing through a cascade of stages, each of which implements part of the program plan, as shown in figure 2.7. In sum- odd-squares , we begin with an enumerator , which generates a “signal” consisting of the leaves of a given tree. This signal is passed through a filter , which eliminates all but the odd elements. The resulting signal is in turn passed through a map, which is a “transducer” that applies the square procedure to each element. The output of the map is then fed to anaccumulator , which combines the elements using +, starting from an initial 0. The plan for even-fibs  is analogous. Figur e 2.7:   The signal-flow plans for the procedures sum-odd-squares (top) and even-fibs  (bottom) reveal the commonality between the two programs.


key: chroma:document
string_value:
 Unfortunately , the two procedure definitions above fail to exhibit this signal-flow structure. For instance, if we examine the sum-odd-squares procedure, we find that the enumeration is implemented partly by the null? and pair?  tests and partly by the tree-recursive structure of the procedure. Similarly , the accumulation is found partly in the tests and partly in the addition used in the recursion. In general, there are no distinct parts of either procedure that correspond to the elements in the signal-flow description. Our two procedures decompose the computations in a dif ferent way, spreading the enumeration over the program and mingling it with the map, the filter , and the accumulation. If we could or ganize our programs to make the signal-flow structure manifest in the procedures we write, this would increase the conceptual clarity of the resulting code. Sequence Operations The key to or ganizing programs so as to more clearly reflect the signal-flow structure is to concentrate on the “signals” that


key: chroma:document
string_value:
 flow from one stage in the process to the next. If we represent these signals as lists, then we can use list operations to implement the processing at each of the stages. For instance, we can implement the mapping stages of the signal-flow diagrams using the map procedure from section 2.2.1 : (map square (list 1 2 3 4 5))   (1 4 9 16 25)  Filtering a sequence to select only those elements that satisfy a given predicate is accomplished by (define (filter predicate sequence)     (cond ((null? sequence) nil)           ((predicate (car sequence))            (cons (car sequence)                  (filter predicate (cdr sequence))))           (else (filter predicate (cdr sequence)))))   For example, (filter odd? (list 1 2 3 4 5))   (1 3 5)  Accumulations can be implemented by (define (accumulate op initial sequence)     (if (null? sequence)         initial         (op (car sequence)             (accumulate op initial (cdr sequence)))))   (accumulate + 0 (list 1 2 3 4 5))   15  (accumulate * 1 (list 1 2 3 4 5))   12


key: chroma:document
string_value:
0  (accumulate cons nil (list 1 2 3 4 5))   (1 2 3 4 5)   All that remains to implement signal-flow diagrams is to enumerate the sequence of elements to be processed. For even-fibs , we need to generate the sequence of integers in a given range, which we can do as follows: (define (enumerate-interval low high)     (if (> low high)         nil         (cons low (enumerate-interval (+ low 1) high))))  (enumerate-interval 2 7)   (2 3 4 5 6 7)   To enumerate the leaves of a tree, we can use14 (define (enumerate-tree tree)     (cond ((null? tree) nil)           ((not (pair? tree)) (list tree))           (else (append (enumerate-tree (car tree))                         (enumerate-tree (cdr tree))))))   (enumerate-tree (list 1 (list 2 (list 3 4)) 5))   (1 2 3 4 5)   Now we can reformulate sum-odd-squares  and even-fibs  as in the signal- flow diagrams. For sum-odd-squares , we enumerate the sequence of leaves of the tree, filter this to keep only the odd numbers in the sequence, square each element, and sum the resu


key: chroma:document
string_value:
lts: (define (sum-odd-squares tree)     (accumulate +                 0                 (map square                      (filter odd?                              (enumerate-tree tree)))))   For even-fibs , we enumerate the integers from 0 to n, generate the Fibonacci number for each of these integers, filter the resulting sequence to keep only the even elements, and accumulate the results into a list: (define (even-fibs n)     (accumulate cons                 nil                 (filter even?                         (map fib                              (enumerate-interval 0 n)))))   The value of expressing programs as sequence operations is that this helps us make program designs that are modular , that is, designs that areconstructed by combining relatively independent pieces. We can encourage modular design by providing a library of standard components together with a conventional interface for connecting the components in flexible ways. Modular construction is a powerful strategy for controlling complexi


key: chroma:document
string_value:
ty in engineering design. In real signal-processing applications, for example, designers regularly build systems by cascading elements selected from standardized families of filters and transducers. Similarly , sequence operations provide a library of standard program elements that we can mix and match. For instance, we can reuse pieces from the sum-odd-squares and even-fibs  procedures in a program that constructs a list of the squares of the first n + 1 Fibonacci numbers: (define (list-fib-squares n)     (accumulate cons                 nil                 (map square                      (map fib                           (enumerate-interval 0 n)))))   (list-fib-squares 10)   (0 1 1 4 9 25 64 169 441 1156 3025)   We can rearrange the pieces and use them in computing the product of the odd integers in a sequence: (define (product-of-squares-of-odd-elements sequence)     (accumulate *                 1                 (map square                      (filter odd? sequence))))   (product-of-squares-of-odd-ele


key: chroma:document
string_value:
ments (list 1 2 3 4 5))   225  We can also formulate conventional data-processing applications in terms of sequence operations. Suppose we have a sequence of personnel records and we want to find the salary of the highest-paid programmer . Assume thatwe have a selector salary  that returns the salary of a record, and a predicate programmer?  that tests if a record is for a programmer . Then we can write (define (salary-of-highest-paid-programmer records)     (accumulate max                 0                 (map salary                      (filter programmer? records))))   These examples give just a hint of the vast range of operations that can be expressed as sequence operations.15 Sequences, implemented here as lists, serve as a conventional interface that permits us to combine processing modules. Additionally , when we uniformly represent structures as sequences, we have localized the data- structure dependencies in our programs to a small number of sequence operations. By changing these, we can experiment


key: chroma:document
string_value:
 with alternative representations of sequences, while leaving the overall design of our programs intact. We will exploit this capability in section 3.5, when we generalize the sequence-processing paradigm to admit infinite sequences. Exer cise 2.33.   Fill in the missing expressions to complete the following definitions of some basic list-manipulation operations as accumulations: (define (map p sequence)     (accumulate (lambda (x y) < ??>) nil sequence))   (define (append seq1 seq2)     (accumulate cons < ??> <??>))  (define (length sequence)     (accumulate < ??> 0 sequence))   Exer cise 2.34.   Evaluating a polynomial in x at a given value of x can be formulated as an accumulation. We evaluate the polynomial using a well-known algorithm called Horner's rule , which structures the computation asIn other words, we start with an, multiply by x, add an-1, multiply by x, and so on, until we reach a0.16 Fill in the following template to produce a procedure that evaluates a polynomial using Horner's rule. Assume 


key: chroma:document
string_value:
that the coefficients of the polynomial are arranged in a sequence, from a0 through an. (define (horner-eval x coefficient-sequence)     (accumulate (lambda (this-coeff higher-terms) < ??>)                0                 coefficient-sequence))   For example, to compute 1 + 3 x + 5x3 + x5 at x = 2 you would evaluate (horner-eval 2 (list 1 3 0 5 0 1))   Exer cise 2.35.   Redefine count-leaves  from section 2.2.2  as an accumulation: (define (count-leaves t)     (accumulate < ??> <??> (map < ??> <??>)))  Exer cise 2.36.   The procedure accumulate-n  is similar to accumulate except that it takes as its third ar gument a sequence of sequences, which are all assumed to have the same number of elements. It applies the designated accumulation procedure to combine all the first elements of the sequences, all the second elements of the sequences, and so on, and returns a sequence of the results. For instance, if s is a sequence containing four sequences, ((1 2 3) (4 5 6) (7 8 9) (10 11 12)),  then the value of (accum


key: chroma:document
string_value:
ulate-n + 0 s)  should be the sequence (22 26 30) . Fill in the missing expressions in the following definition of accumulate-n : (define (accumulate-n op init seqs)     (if (null? (car seqs))         nil        (cons (accumulate op init < ??>)              (accumulate-n op init < ??>))))  Exer cise 2.37.   Suppose we represent vectors v = (vi) as sequences of numbers, and matrices m = (mij) as sequences of vectors (the rows of the matrix). For example, the matrix is represented as the sequence ((1 2 3 4) (4 5 6 6) (6 7 8 9)) . With this representation, we can use sequence operations to concisely express the basic matrix and vector operations. These operations (which are described in any book on matrix algebra) are the following: We can define the dot product as17 (define (dot-product v w)     (accumulate + 0 (map * v w)))   Fill in the missing expressions in the following procedures for computing the other matrix operations. (The procedure accumulate-n  is defined in exercise 2.36.) (define (matrix-*-vector 


key: chroma:document
string_value:
m v)     (map < ??> m))  (define (transpose mat)     (accumulate-n < ??> <??> mat))  (define (matrix-*-matrix m n)     (let ((cols (transpose n)))       (map < ??> m))) Exer cise 2.38.   The accumulate  procedure is also known as fold-right , because it combines the first element of the sequence with the result of combining all the elements to the right. There is also a fold-left , which is similar to fold-right , except that it combines elements working in the opposite direction: (define (fold-left op initial sequence)     (define (iter result rest)       (if (null? rest)           result           (iter (op result (car rest))                 (cdr rest))))     (iter initial sequence))   What are the values of (fold-right / 1 (list 1 2 3))   (fold-left / 1 (list 1 2 3))   (fold-right list nil (list 1 2 3))   (fold-left list nil (list 1 2 3))   Give a property that op should satisfy to guarantee that fold-right  and fold-left  will produce the same values for any sequence. Exer cise 2.39.    Complete the follo


key: chroma:document
string_value:
wing definitions of reverse (exercise 2.18) in terms of fold-right  and fold-left  from exercise 2.38: (define (reverse sequence)     (fold-right (lambda (x y) < ??>) nil sequence))   (define (reverse sequence)     (fold-left (lambda (x y) < ??>) nil sequence))   Nested Mappings We can extend the sequence paradigm to include many computations that are commonly expressed using nested loops.18 Consider this problem: Given a positive integer n, find all ordered pairs of distinct positive integers i andj, where 1 < j< i< n, such that i + j is prime. For example, if n is 6, then the pairs are the following: A natural way to or ganize this computation is to generate the sequence of all ordered pairs of positive integers less than or equal to n, filter to select those pairs whose sum is prime, and then, for each pair ( i, j) that passes through the filter , produce the triple ( i,j,i + j). Here is a way to generate the sequence of pairs: For each integer i< n, enumerate the integers j<i, and for each such i and j ge


key: chroma:document
string_value:
nerate the pair ( i,j). In terms of sequence operations, we map along the sequence (enumerate- interval 1 n) . For each i in this sequence, we map along the sequence (enumerate-interval 1 (- i 1)) . For each j in this latter sequence, we generate the pair (list i j) . This gives us a sequence of pairs for each i. Combining all the sequences for all the i (by accumulating with append ) produces the required sequence of pairs:19 (accumulate append               nil               (map (lambda (i)                      (map (lambda (j) (list i j))                           (enumerate-interval 1 (- i 1))))                    (enumerate-interval 1 n)))   The combination of mapping and accumulating with append  is so common in this sort of program that we will isolate it as a separate procedure: (define (flatmap proc seq)     (accumulate append nil (map proc seq)))   Now filter this sequence of pairs to find those whose sum is prime. The filter predicate is called for each element of the sequence; its ar gument is a 


key: chroma:document
string_value:
pair and it must extract the integers from the pair . Thus, the predicate to apply to each element in the sequence is(define (prime-sum? pair)     (prime? (+ (car pair) (cadr pair))))   Finally , generate the sequence of results by mapping over the filtered pairs using the following procedure, which constructs a triple consisting of the two elements of the pair along with their sum: (define (make-pair-sum pair)     (list (car pair) (cadr pair) (+ (car pair) (cadr pair))))   Combining all these steps yields the complete procedure: (define (prime-sum-pairs n)     (map make-pair-sum          (filter prime-sum?                  (flatmap                   (lambda (i)                     (map (lambda (j) (list i j))                          (enumerate-interval 1 (- i 1))))                   (enumerate-interval 1 n)))))   Nested mappings are also useful for sequences other than those that enumerate intervals. Suppose we wish to generate all the permutations of a set S; that is, all the ways of ordering the items in 


key: chroma:document
string_value:
the set. For instance, the permutations of {1,2,3} are {1,2,3}, { 1,3,2}, {2,1,3}, { 2,3,1}, { 3,1,2}, and { 3,2,1}. Here is a plan for generating the permutations of S: For each item x in S, recursively generate the sequence of permutations of S - x,20 and adjoin x to the front of each one. This yields, for each x in S, the sequence of permutations of S that begin with x. Combining these sequences for all x gives all the permutations of S:21 (define (permutations s)     (if (null? s)                    ; empty set?         (list nil)                   ; sequence containing empty  set        (flatmap (lambda (x)                    (map (lambda (p) (cons x p))                        (permutations (remove x s))))                  s)))   Notice how this strategy reduces the problem of generating permutations of S to the problem of generating the permutations of sets with fewer elements than S. In the terminal case, we work our way down to the empty list, which represents a set of no elements. For this, we genera


key: chroma:document
string_value:
te (list nil) , which is a sequence with one item, namely the set with no elements. The remove procedure used in permutations  returns all the items in a given sequence except for a given item. This can be expressed as a simple filter: (define (remove item sequence)     (filter (lambda (x) (not (= x item)))             sequence))   Exer cise 2.40.   Define a procedure unique-pairs  that, given an integer n, generates the sequence of pairs ( i,j) with 1 < j< i< n. Use unique-pairs  to simplify the definition of prime-sum-pairs  given above. Exer cise 2.41.   Write a procedure to find all ordered triples of distinct positive integers i, j, and k less than or equal to a given integer n that sum to a given integer s. Exer cise 2.42.    Figur e 2.8:   A solution to the eight-queens puzzle.The “eight-queens puzzle” asks how to place eight queens on a chessboard so that no queen is in check from any other (i.e., no two queens are in the same row , column, or diagonal). One possible solution is shown in figure 2.8. O


key: chroma:document
string_value:
ne way to solve the puzzle is to work across the board, placing a queen in each column. Once we have placed k - 1 queens, we must place the kth queen in a position where it does not check any of the queens already on the board. We can formulate this approach recursively: Assume that we have already generated the sequence of all possible ways to place k - 1 queens in the first k - 1 columns of the board. For each of these ways, generate an extended set of positions by placing a queen in each row of the kth column. Now filter these, keeping only the positions for which the queen in the kth column is safe with respect to the other queens. This produces the sequence of all ways to place k queens in the first k columns. By continuing this process, we will produce not only one solution, but all solutions to the puzzle. We implement this solution as a procedure queens , which returns a sequence of all solutions to the problem of placing n queens on an n× n chessboard. Queens  has an internal procedure queen-cols  th


key: chroma:document
string_value:
at returns the sequence of all ways to place queens in the first k columns of the board. (define (queens board-size)     (define (queen-cols k)        (if (= k 0)          (list empty-board)           (filter            (lambda (positions) (safe? k positions))            (flatmap             (lambda (rest-of-queens)               (map (lambda (new-row)                      (adjoin-position new-row k rest-of-queens))                    (enumerate-interval 1 board-size)))             (queen-cols (- k 1))))))     (queen-cols board-size))   In this procedure rest-of-queens  is a way to place k - 1 queens in the first k - 1 columns, and new-row  is a proposed row in which to place the queen for the kth column. Complete the program by implementing the representation for sets of board positions, including the procedure adjoin- position , which adjoins a new row-column position to a set of positions, and empty-board , which represents an empty set of positions. You must also write the procedure safe? , which determin


key: chroma:document
string_value:
es for a set of positions, whether the queen in the kth column is safe with respect to the others. (Note that we need only check whether the new queen is safe – the other queens are already guaranteed safe with respect to each other .) Exer cise 2.43.   Louis Reasoner is having a terrible time doing exercise 2.42. His queens  procedure seems to work, but it runs extremely slowly . (Louis never does manage to wait long enough for it to solve even the 6× 6 case.) When Louis asks Eva Lu Ator for help, she points out that he has interchanged the order of the nested mappings in the flatmap , writing it as (flatmap    (lambda (new-row)      (map (lambda (rest-of-queens)             (adjoin-position new-row k rest-of-queens))           (queen-cols (- k 1))))    (enumerate-interval 1 board-size))   Explain why this interchange makes the program run slowly . Estimate how long it will take Louis's program to solve the eight-queens puzzle, assumingthat the program in exercise 2.42 solves the puzzle in time T. 2.2.4  Exa


key: chroma:document
string_value:
mple: A Pictur e Language This section presents a simple language for drawing pictures that illustrates the power of data abstraction and closure, and also exploits higher -order procedures in an essential way . The language is designed to make it easy to experiment with patterns such as the ones in figure 2.9, which are composed of repeated elements that are shifted and scaled.22 In this language, the data objects being combined are represented as procedures rather than as list structure. Just as cons , which satisfies the closure property , allowed us to easily build arbitrarily complicated list structure, the operations in this language, which also satisfy the closure property , allow us to easily build arbitrarily complicated patterns.                Figur e 2.9:   Designs generated with the picture language. The pictur e language When we began our study of programming in section 1.1, we emphasized the importance of describing a language by focusing on the language's primitives, its means of combination, 


key: chroma:document
string_value:
and its means of abstraction. We'll follow that framework here. Part of the elegance of this picture language is that there is only one kind of element, called a painter . A painter draws an image that is shifted and scaled to fit within a designated parallelogram-shaped frame. For example, there's a primitive painter we'll call wave  that makes a crude line drawing,as shown in figure 2.10. The actual shape of the drawing depends on the frame – all four images in figure 2.10 are produced by the same wave painter , but with respect to four dif ferent frames. Painters can be more elaborate than this: The primitive painter called rogers  paints a picture of MIT's founder , William Barton Rogers, as shown in figure 2.11.23 The four images in figure 2.11 are drawn with respect to the same four frames as the wave  images in figure 2.10. To combine images, we use various operations that construct new painters from given painters. For example, the beside  operation takes two painters and produces a new , compound pai


key: chroma:document
string_value:
nter that draws the first painter's image in the left half of the frame and the second painter's image in the right half of the frame. Similarly , below  takes two painters and produces a compound painter that draws the first painter's image below the second painter's image. Some operations transform a single painter to produce a new painter . For example, flip-vert  takes a painter and produces a painter that draws its image upside-down, and flip-horiz  produces a painter that draws the original painter's image left-to-right reversed.                Figur e 2.10:   Images produced by the wave  painter , with respect to four different frames. The frames, shown with dotted lines, are not part of the images.                                               Figur e 2.1 1:  Images of W illiam Barton Rogers, founder and first president of MIT , painted with respect to the same four frames as in figure 2.10 (original image reprinted with the permission of the MIT Museum). Figure 2.12 shows the drawing of a painter cal


key: chroma:document
string_value:
led wave4  that is built up in two stages starting from wave : (define wave2 (beside wave (flip-vert wave)))   (define wave4 (below wave2 wave2))   Figur e 2.12:   Creating a complex figure, starting from the wave  painter of figure 2.10.                (define wave2                         (define wave4     (beside wave (flip-vert wave)))       (below wave2 wave2))   In building up a complex image in this manner we are exploiting the fact that painters are closed under the language's means of combination. The beside  or below  of two painters is itself a painter; therefore, we can use it as an element in making more complex painters. As with building up list structure using cons , the closure of our data under the means of combination is crucial to the ability to create complex structures while using only a few operations. Once we can combine painters, we would like to be able to abstract typical patterns of combining painters. We will implement the painter operations as Scheme procedures. This means that we


key: chroma:document
string_value:
 don't need a special abstraction mechanism in the picture language: Since the means of combination are ordinary Scheme procedures, we automatically have the capability to do anything with painter operations that we can do with procedures. For example, we can abstract the pattern in wave4  as (define (flipped-pairs painter)     (let ((painter2 (beside painter (flip-vert painter))))       (below painter2 painter2)))   and define wave4  as an instance of this pattern: (define wave4 (flipped-pairs wave))  We can also define recursive operations. Here's one that makes painters split and branch towards the right as shown in figures 2.13 and  2.14: (define (right-split painter n)     (if (= n 0)         painter         (let ((smaller (right-split painter (- n 1))))           (beside painter (below smaller smaller)))))                       right-split n                   corner-split n Figur e 2.13:   Recursive plans for right-split  and corner-split . We can produce balanced patterns by branching upwards as well a


key: chroma:document
string_value:
s towards the right (see exercise 2.44 and figures 2.13 and  2.14): (define (corner-split painter n)     (if (= n 0)         painter         (let ((up (up-split painter (- n 1)))               (right (right-split painter (- n 1))))           (let ((top-left (beside up up))                 (bottom-right (below right right))                 (corner (corner-split painter (- n 1))))             (beside (below painter top-left)                     (below bottom-right corner))))))   Figur e 2.14:   The recursive operations right-split  and corner-split                     (right-split wave 4)         (right-split rogers 4)                       (corner-split wave 4)         (corner-split rogers 4)  applied to the painters wave  and rogers . Combining four corner-split figures produces symmetric square-limit  designs as shown in figure 2.9. By placing four copies of a corner-split  appropriately , we obtain a pattern called square-limit , whose application to wave  and rogers  is shown in figure 2.9: (define (square


key: chroma:document
string_value:
-limit painter n)     (let ((quarter (corner-split painter n)))       (let ((half (beside (flip-horiz quarter) quarter)))         (below (flip-vert half) half))))   Exer cise 2.44.   Define the procedure up-split  used by corner-split . It is similar to right-split , except that it switches the roles of below  and beside .Higher -order operations In addition to abstracting patterns of combining painters, we can work at a higher level, abstracting patterns of combining painter operations. That is, we can view the painter operations as elements to manipulate and can write means of combination for these elements – procedures that take painter operations as ar guments and create new painter operations. For example, flipped-pairs  and square-limit  each arrange four copies of a painter's image in a square pattern; they dif fer only in how they orient the copies. One way to abstract this pattern of painter combination is with the following procedure, which takes four one-ar gument painter operations and produces a 


key: chroma:document
string_value:
painter operation that transforms a given painter with those four operations and arranges the results in a square. Tl, tr, bl, and br are the transformations to apply to the top left copy , the top right copy , the bottom left copy , and the bottom right copy , respectively . (define (square-of-four tl tr bl br)     (lambda (painter)       (let ((top (beside (tl painter) (tr painter)))             (bottom (beside (bl painter) (br painter))))         (below bottom top))))   Then flipped-pairs  can be defined in terms of square-of-four  as follows:24 (define (flipped-pairs painter)     (let ((combine4 (square-of-four identity flip-vert                                     identity flip-vert)))       (combine4 painter)))   and square-limit  can be expressed as25 (define (square-limit painter n)     (let ((combine4 (square-of-four flip-horiz identity                                     rotate180 flip-vert)))       (combine4 (corner-split painter n))))  Exer cise 2.45.   Right-split  and up-split  can be expressed 


key: chroma:document
string_value:
as instances of a general splitting operation. Define a procedure split  with the property that evaluating (define right-split (split beside below))   (define up-split (split below beside))   produces procedures right-split  and up-split  with the same behaviors as the ones already defined. Frames Before we can show how to implement painters and their means of combination, we must first consider frames. A frame can be described by three vectors – an origin vector and two edge vectors. The origin vector specifies the of fset of the frame's origin from some absolute origin in the plane, and the edge vectors specify the of fsets of the frame's corners from its origin. If the edges are perpendicular , the frame will be rectangular . Otherwise the frame will be a more general parallelogram. Figure 2.15 shows a frame and its associated vectors. In accordance with data abstraction, we need not be specific yet about how frames are represented, other than to say that there is a constructor make-frame , which takes thr


key: chroma:document
string_value:
ee vectors and produces a frame, and three corresponding selectors origin-frame , edge1-frame , and edge2-frame  (see exercise 2.47). Figur e 2.15:   A frame is described by three vectors – an origin and two edges.We will use coordinates in the unit square (0 < x,y< 1) to specify images. With each frame, we associate a frame coor dinate map , which will be used to shift and scale images to fit the frame. The map transforms the unit square into the frame by mapping the vector v = (x,y) to the vector sum For example, (0,0) is mapped to the origin of the frame, (1,1) to the vertex diagonally opposite the origin, and (0.5,0.5) to the center of the frame. We can create a frame's coordinate map with the following procedure:26 (define (frame-coord-map frame)     (lambda (v)       (add-vect        (origin-frame frame)        (add-vect (scale-vect (xcor-vect v)                              (edge1-frame frame))                  (scale-vect (ycor-vect v)                              (edge2-frame frame))))))   Observe th


key: chroma:document
string_value:
at applying frame-coord-map  to a frame returns a procedure that, given a vector , returns a vector . If the ar gument vector is in the unit square, the result vector will be in the frame. For example, ((frame-coord-map a-frame) (make-vect 0 0))   returns the same vector as (origin-frame a-frame)   Exer cise 2.46.   A two-dimensional vector v running from the origin to a point can be represented as a pair consisting of an x-coordinate and a y- coordinate. Implement a data abstraction for vectors by giving a constructor make-vect  and corresponding selectors xcor-vect  and ycor-vect . In terms of your selectors and constructor , implement procedures add-vect , sub- vect , and scale-vect  that perform the operations vector addition, vector subtraction, and multiplying a vector by a scalar:Exer cise 2.47.   Here are two possible constructors for frames: (define (make-frame origin edge1 edge2)     (list origin edge1 edge2))     (define (make-frame origin edge1 edge2)     (cons origin (cons edge1 edge2)))   For ea


key: chroma:document
string_value:
ch constructor supply the appropriate selectors to produce an implementation for frames. Painters A painter is represented as a procedure that, given a frame as ar gument, draws a particular image shifted and scaled to fit the frame. That is to say , if p is a painter and f is a frame, then we produce p's image in f by calling p with f as ar gument. The details of how primitive painters are implemented depend on the particular characteristics of the graphics system and the type of image to be drawn. For instance, suppose we have a procedure draw-line  that draws a line on the screen between two specified points. Then we can create painters for line drawings, such as the wave  painter in figure 2.10, from lists of line segments as follows:27 (define (segments->painter segment-list)     (lambda (frame)       (for-each        (lambda (segment)          (draw-line           ((frame-coord-map frame) (start-segment segment))           ((frame-coord-map frame) (end-segment segment))))        segment-list)))  The seg


key: chroma:document
string_value:
ments are given using coordinates with respect to the unit square. For each segment in the list, the painter transforms the segment endpoints with the frame coordinate map and draws a line between the transformed points. Representing painters as procedures erects a powerful abstraction barrier in the picture language. We can create and intermix all sorts of primitive painters, based on a variety of graphics capabilities. The details of their implementation do not matter . Any procedure can serve as a painter , provided that it takes a frame as ar gument and draws something scaled to fit the frame.28 Exer cise 2.48.   A directed line segment in the plane can be represented as a pair of vectors – the vector running from the origin to the start-point of the segment, and the vector running from the origin to the end-point of the segment. Use your vector representation from exercise 2.46 to define a representation for segments with a constructor make-segment  and selectors start-segment  and end-segment . Exer cis


key: chroma:document
string_value:
e 2.49.   Use segments->painter  to define the following primitive painters: a.  The painter that draws the outline of the designated frame. b.  The painter that draws an “X” by connecting opposite corners of the frame. c.  The painter that draws a diamond shape by connecting the midpoints of the sides of the frame. d.  The wave  painter . Transforming and combining painters An operation on painters (such as flip-vert  or beside ) works by creating a painter that invokes the original painters with respect to frames derived from the ar gument frame. Thus, for example, flip-vert  doesn't have to know how a painter works in order to flip it – it just has to know how toturn a frame upside down: The flipped painter just uses the original painter , but in the inverted frame. Painter operations are based on the procedure transform-painter , which takes as ar guments a painter and information on how to transform a frame and produces a new painter . The transformed painter , when called on a frame, transforms the fram


key: chroma:document
string_value:
e and calls the original painter on the transformed frame. The ar guments to transform-painter  are points (represented as vectors) that specify the corners of the new frame: When mapped into the frame, the first point specifies the new frame's origin and the other two specify the ends of its edge vectors. Thus, ar guments within the unit square specify a frame contained within the original frame. (define (transform-painter painter origin corner1 corner2)     (lambda (frame)       (let ((m (frame-coord-map frame)))         (let ((new-origin (m origin)))           (painter            (make-frame new-origin                        (sub-vect (m corner1) new-origin)                        (sub-vect (m corner2) new-origin)))))))   Here's how to flip painter images vertically: (define (flip-vert painter)     (transform-painter painter                        (make-vect 0.0 1.0)   ; new origin                       (make-vect 1.0 1.0)   ; new end of edge1                       (make-vect 0.0 0.0))) ; new end of edge2 


key: chroma:document
string_value:
 Using transform-painter , we can easily define new transformations. For example, we can define a painter that shrinks its image to the upper -right quarter of the frame it is given: (define (shrink-to-upper-right painter)     (transform-painter painter                        (make-vect 0.5 0.5)                       (make-vect 1.0 0.5)                        (make-vect 0.5 1.0)))   Other transformations rotate images counterclockwise by 90 degrees29 (define (rotate90 painter)     (transform-painter painter                        (make-vect 1.0 0.0)                        (make-vect 1.0 1.0)                        (make-vect 0.0 0.0)))   or squash images towards the center of the frame:30 (define (squash-inwards painter)     (transform-painter painter                        (make-vect 0.0 0.0)                        (make-vect 0.65 0.35)                        (make-vect 0.35 0.65)))   Frame transformation is also the key to defining means of combining two or more painters. The beside  procedure, for example,


key: chroma:document
string_value:
 takes two painters, transforms them to paint in the left and right halves of an ar gument frame respectively , and produces a new , compound painter . When the compound painter is given a frame, it calls the first transformed painter to paint in the left half of the frame and calls the second transformed painter to paint in the right half of the frame: (define (beside painter1 painter2)     (let ((split-point (make-vect 0.5 0.0)))       (let ((paint-left              (transform-painter painter1                                 (make-vect 0.0 0.0)                                 split-point                                 (make-vect 0.0 1.0)))             (paint-right              (transform-painter painter2                                 split-point                                 (make-vect 1.0 0.0)                                (make-vect 0.5 1.0))))         (lambda (frame)           (paint-left frame)           (paint-right frame)))))   Observe how the painter data abstraction, and in particular the repr


key: chroma:document
string_value:
esentation of painters as procedures, makes beside  easy to implement. The beside  procedure need not know anything about the details of the component painters other than that each painter will draw something in its designated frame. Exer cise 2.50.   Define the transformation flip-horiz , which flips painters horizontally , and transformations that rotate painters counterclockwise by 180 degrees and 270 degrees. Exer cise 2.51.   Define the below  operation for painters. Below  takes two painters as ar guments. The resulting painter , given a frame, draws with the first painter in the bottom of the frame and with the second painter in the top. Define below  in two dif ferent ways – first by writing a procedure that is analogous to the beside  procedure given above, and again in terms of beside  and suitable rotation operations (from exercise 2.50). Levels of language for r obust design The picture language exercises some of the critical ideas we've introduced about abstraction with procedures and data. The f


key: chroma:document
string_value:
undamental data abstractions, painters, are implemented using procedural representations, which enables the language to handle dif ferent basic drawing capabilities in a uniform way . The means of combination satisfy the closure property , which permits us to easily build up complex designs. Finally , all the tools for abstracting procedures are available to us for abstracting means of combination for painters. We have also obtained a glimpse of another crucial idea about languages and program design. This is the approach of stratified design , the notion that a complex system should be structured as a sequence of levels that are described using a sequence of languages. Each level is constructed by combining parts that are regarded as primitive at that level, and the partsconstructed at each level are used as primitives at the next level. The language used at each level of a stratified design has primitives, means of combination, and means of abstraction appropriate to that level of detail. Stratified design 


key: chroma:document
string_value:
pervades the engineering of complex systems. For example, in computer engineering, resistors and transistors are combined (and described using a language of analog circuits) to produce parts such as and-gates and or -gates, which form the primitives of a language for digital- circuit design.31 These parts are combined to build processors, bus structures, and memory systems, which are in turn combined to form computers, using languages appropriate to computer architecture. Computers are combined to form distributed systems, using languages appropriate for describing network interconnections, and so on. As a tiny example of stratification, our picture language uses primitive elements (primitive painters) that are created using a language that specifies points and lines to provide the lists of line segments for segments- >painter , or the shading details for a painter like rogers . The bulk of our description of the picture language focused on combining these primitives, using geometric combiners such as beside 


key: chroma:document
string_value:
 and below . We also worked at a higher level, regarding beside  and below  as primitives to be manipulated in a language whose operations, such as square-of-four , capture common patterns of combining geometric combiners. Stratified design helps make programs robust , that is, it makes it likely that small changes in a specification will require correspondingly small changes in the program. For instance, suppose we wanted to change the image based on wave  shown in figure 2.9. We could work at the lowest level to change the detailed appearance of the wave  element; we could work at the middle level to change the way corner-split  replicates the wave ; we could work at the highest level to change how square-limit  arranges the four copies of the corner . In general, each level of a stratified design provides a dif ferent vocabulary for expressing the characteristics of the system, and a dif ferent kind of ability to change it. Exer cise 2.52.   Make changes to the square limit of wave  shown in figure 2.9 by 


key: chroma:document
string_value:
working at each of the levels described above. In particular:a.  Add some segments to the primitive wave  painter of exercise  2.49 (to add a smile, for example). b.  Change the pattern constructed by corner-split  (for example, by using only one copy of the up-split  and right-split  images instead of two). c.  Modify the version of square-limit  that uses square-of-four  so as to assemble the corners in a dif ferent pattern. (For example, you might make the big Mr . Rogers look outward from each corner of the square.) 6 The use of the word “closure” here comes from abstract algebra, where a set of elements is said to be closed under an operation if applying the operation to elements in the set produces an element that is again an element of the set. The Lisp community also (unfortunately) uses the word “closure” to describe a totally unrelated concept: A closure is an implementation technique for representing procedures with free variables. We do not use the word “closure” in this second sense in this book.


key: chroma:document
string_value:
 7 The notion that a means of combination should satisfy closure is a straightforward idea. Unfortunately , the data combiners provided in many popular programming languages do not satisfy closure, or make closure cumbersome to exploit. In Fortran or Basic, one typically combines data elements by assembling them into arrays – but one cannot form arrays whose elements are themselves arrays. Pascal and C admit structures whose elements are structures. However , this requires that the programmer manipulate pointers explicitly , and adhere to the restriction that each field of a structure can contain only elements of a prespecified form. Unlike Lisp with its pairs, these languages have no built-in general-purpose glue that makes it easy to manipulate compound data in a uniform way . This limitation lies behind Alan Perlis's comment in his foreword to this book: “In Pascal the plethora of declarable data structures induces a specialization within functions that inhibits and penalizes casual cooperation. It is bett


key: chroma:document
string_value:
er to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures.” 8 In this book, we use list to mean a chain of pairs terminated by the end-of-list marker . In contrast, the term list structur e refers to any data structure made out of pairs, not just to lists. 9 Since nested applications of car and cdr are cumbersome to write, Lisp dialects provide abbreviations for them – for instance, The names of all such procedures start with c and end with r. Each a between them stands for a car operation and each d for a cdr operation, to be applied in the same order in which they appear in the name. The names car and cdr persist because simple combinations like cadr  are pronounceable. 10 It's remarkable how much ener gy in the standardization of Lisp dialects has been dissipated in arguments that are literally over nothing: Should nil be an ordinary name? Should the value of nil be a symbol? Should it be a list? Should it be a pair? In Scheme, nil is an ordinary name, w


key: chroma:document
string_value:
hich we use in this section as a variable whose value is the end-of-list marker (just as true  is an ordinary variable that has a true value). Other dialects of Lisp, including Common Lisp, treat nil as a specialsymbol. The authors of this book, who have endured too many language standardization brawls, would like to avoid the entire issue. Once we have introduced quotation in section 2.3, we will denote the empty list as '() and dispense with the variable nil entirely . 11 To define f and g using lambda  we would write (define f (lambda (x y . z) < body>))  (define g (lambda w < body>))  12 Scheme standardly provides a map procedure that is more general than the one described here. This more general map takes a procedure of n arguments, together with n lists, and applies the procedure to all the first elements of the lists, all the second elements of the lists, and so on, returning a list of the results. For example: (map + (list 1 2 3) (list 40 50 60) (list 700 800 900))   (741 852 963)     (map (lambda (x 


key: chroma:document
string_value:
y) (+ x (* 2 y)))        (list 1 2 3)        (list 4 5 6))   (9 12 15)   13 The order of the first two clauses in the cond  matters, since the empty list satisfies null?  and also is not a pair . 14 This is, in fact, precisely the fringe  procedure from exercise 2.28. Here we've renamed it to emphasize that it is part of a family of general sequence-manipulation procedures. 15 Richard W aters (1979) developed a program that automatically analyzes traditional Fortran programs, viewing them in terms of maps, filters, and accumulations. He found that fully 90 percent of the code in the Fortran Scientific Subroutine Package fits neatly into this paradigm. One of the reasons for the success of Lisp as a programming language is that lists provide a standard medium for expressing ordered collections so that they can be manipulated using higher -order operations. The programming language APL owes much of its power and appeal to a similar choice. In APL all data are represented as arrays, and there is a universal and 


key: chroma:document
string_value:
convenient set of generic operators for all sorts of array operations. 16 According to Knuth (1981), this rule was formulated by W. G. Horner early in the nineteenth century , but the method was actually used by Newton over a hundred years earlier . Horner's rule evaluates the polynomial using fewer additions and multiplications than does the straightforward method of first computing anxn, then adding an-1xn-1, and so on. In fact, it is possible to prove that any algorithm for evaluating arbitrary polynomials must use at least as many additions and multiplications as does Horner's rule, and thus Horner's rule is an optimal algorithm for polynomial evaluation. This was proved (for the number of additions) by A. M. Ostrowski in a 1954 paper that essentially founded the modern study of optimal algorithms. The analogous statement for multiplications was proved by V. Y. Pan in 1966. The book by Borodin and Munro (1975) provides an overview of these and other results about optimal algorithms. 17 This definition use


key: chroma:document
string_value:
s the extended version of map described in footnote 12.18 This approach to nested mappings was shown to us by David T urner , whose languages KRC and Miranda provide elegant formalisms for dealing with these constructs. The examples in this section (see also exercise 2.42) are adapted from T urner 1981. In section 3.5.3 , we'll see how this approach generalizes to infinite sequences. 19 We're representing a pair here as a list of two elements rather than as a Lisp pair . Thus, the “pair” (i,j) is represented as (list i j) , not (cons i j) . 20 The set S - x is the set of all elements of S, excluding x. 21 Semicolons in Scheme code are used to introduce comments . Everything from the semicolon to the end of the line is ignored by the interpreter . In this book we don't use many comments; we try to make our programs self-documenting by using descriptive names. 22 The picture language is based on the language Peter Henderson created to construct images like M.C. Escher's “Square Limit” woodcut (see Henderson 198


key: chroma:document
string_value:
2). The woodcut incorporates a repeated scaled pattern, similar to the arrangements drawn using the square-limit  procedure in this section. 23 William Barton Rogers (1804-1882) was the founder and first president of MIT . A geologist and talented teacher , he taught at W illiam and Mary College and at the University of V irginia. In 1859 he moved to Boston, where he had more time for research, worked on a plan for establishing a “polytechnic institute,” and served as Massachusetts's first State Inspector of Gas Meters. When MIT was established in 1861, Rogers was elected its first president. Rogers espoused an ideal of “useful learning” that was dif ferent from the university education of the time, with its overemphasis on the classics, which, as he wrote, “stand in the way of the broader , higher and more practical instruction and discipline of the natural and social sciences.” This education was likewise to be dif ferent from narrow trade-school education. In Rogers's words: The world-enforced distinction 


key: chroma:document
string_value:
between the practical and the scientific worker is utterly futile, and the whole experience of modern times has demonstrated its utter worthlessness. Rogers served as president of MIT until 1870, when he resigned due to ill health. In 1878 the second president of MIT , John Runkle, resigned under the pressure of a financial crisis brought on by the Panic of 1873 and strain of fighting of f attempts by Harvard to take over MIT . Rogers returned to hold the of fice of president until 1881. Rogers collapsed and died while addressing MIT's graduating class at the commencement exercises of 1882. Runkle quoted Rogers's last words in a memorial address delivered that same year: “As I stand here today and see what the Institute is, ... I call to mind the beginnings of science. I remember one hundred and fifty years ago Stephen Hales published a pamphlet on the subject of illuminating gas, in which he stated that his researches had demonstrated that 128 grains of bituminous coal – ” “Bituminous coal,” these were his l


key: chroma:document
string_value:
ast words on earth. Here he bent forward, as if consulting some notes on the table before him, then slowly regaining an erect position, threw up his hands, and was translated from the scene of his earthly labors and triumphs to “the tomorrow of death,” where the mysteries of life are solved, and the disembodied spirit finds unending satisfaction in contemplating the new and still unfathomable mysteries of the infinite future.In the words of Francis A. W alker (MIT's third president): All his life he had borne himself most faithfully and heroically , and he died as so good a knight would surely have wished, in harness, at his post, and in the very part and act of public duty . 24 Equivalently , we could write (define flipped-pairs     (square-of-four identity flip-vert identity flip-vert))   25 Rotate180  rotates a painter by 180 degrees (see exercise 2.50). Instead of rotate180  we could say (compose flip-vert flip-horiz) , using the compose  procedure from exercise 1.42. 26 Frame-coord-map  uses the vector o


key: chroma:document
string_value:
perations described in exercise 2.46 below , which we assume have been implemented using some representation for vectors. Because of data abstraction, it doesn't matter what this vector representation is, so long as the vector operations behave correctly . 27 Segments->painter  uses the representation for line segments described in exercise 2.48 below . It also uses the for-each  procedure described in exercise 2.23. 28 For example, the rogers  painter of figure 2.11 was constructed from a gray-level image. For each point in a given frame, the rogers  painter determines the point in the image that is mapped to it under the frame coordinate map, and shades it accordingly . By allowing dif ferent types of painters, we are capitalizing on the abstract data idea discussed in section 2.1.3 , where we ar gued that a rational-number representation could be anything at all that satisfies an appropriate condition. Here we're using the fact that a painter can be implemented in any way at all, so long as it draws someth


key: chroma:document
string_value:
ing in the designated frame. Section 2.1.3  also showed how pairs could be implemented as procedures. Painters are our second example of a procedural representation for data. 29 Rotate90  is a pure rotation only for square frames, because it also stretches and shrinks the image to fit into the rotated frame. 30 The diamond-shaped images in figures 2.10 and 2.11 were created with squash-inwards  applied to wave  and rogers . 31 Section 3.3.4  describes one such language.2.3  Symbolic Data All the compound data objects we have used so far were constructed ultimately from numbers. In this section we extend the representational capability of our language by introducing the ability to work with arbitrary symbols as data. 2.3.1  Quotation If we can form compound data using symbols, we can have lists such as (a b c d)   (23 45 17)   ((Norah 12) (Molly 9) (Anna 7) (Lauren 6) (Charlotte 4))   Lists containing symbols can look just like the expressions of our language: (* (+ 23 45) (+ x 9))     (define (fact n) (if (= 


key: chroma:document
string_value:
n 1) 1 (* n (fact (- n 1)))))   In order to manipulate symbols we need a new element in our language: the ability to quote  a data object. Suppose we want to construct the list (a b) . We can't accomplish this with (list a b) , because this expression constructs a list of the values  of a and b rather than the symbols themselves. This issue is well known in the context of natural languages, where words and sentences may be regarded either as semantic entities or as character strings (syntactic entities). The common practice in natural languages is to use quotation marks to indicate that a word or a sentence is to be treated literally as a string of characters. For instance, the first letter of “John” is clearly “J.” If we tell somebody “say your name aloud,” we expect to hear that person's name. However , if we tell somebody “say `your name' aloud,” we expect to hear the words “your name.” Note that we are forced to nest quotation marks to describe what somebody else might say .32We can follow this same pract


key: chroma:document
string_value:
ice to identify lists and symbols that are to be treated as data objects rather than as expressions to be evaluated. However , our format for quoting dif fers from that of natural languages in that we place a quotation mark (traditionally , the single quote symbol ') only at the beginning of the object to be quoted. We can get away with this in Scheme syntax because we rely on blanks and parentheses to delimit objects. Thus, the meaning of the single quote character is to quote the next object.33 Now we can distinguish between symbols and their values: (define a 1)     (define b 2)     (list a b)   (1 2)    (list 'a 'b)   (a b)    (list 'a b)   (a 2)  Quotation also allows us to type in compound objects, using the conventional printed representation for lists:34 (car '(a b c))   a    (cdr '(a b c))   (b c)  In keeping with this, we can obtain the empty list by evaluating '(), and thus dispense with the variable nil. One additional primitive used in manipulating symbols is eq?, which takes two symbols as ar gu


key: chroma:document
string_value:
ments and tests whether they are the same.35 Using eq?, we can implement a useful procedure called memq . This takes twoarguments, a symbol and a list. If the symbol is not contained in the list (i.e., is not eq? to any item in the list), then memq  returns false. Otherwise, it returns the sublist of the list beginning with the first occurrence of the symbol: (define (memq item x)     (cond ((null? x) false)           ((eq? item (car x)) x)           (else (memq item (cdr x)))))   For example, the value of (memq 'apple '(pear banana prune))   is false, whereas the value of (memq 'apple '(x (apple sauce) y apple pear))   is (apple pear) . Exer cise 2.53.   What would the interpreter print in response to evaluating each of the following expressions? (list 'a 'b 'c)     (list (list 'george))   (cdr '((x1 x2) (y1 y2)))     (cadr '((x1 x2) (y1 y2)))   (pair? (car '(a short list)))   (memq 'red '((red shoes) (blue socks)))     (memq 'red '(red shoes blue socks))   Exer cise 2.54.   Two lists are said to be equal?  


key: chroma:document
string_value:
if they contain equal elements arranged in the same order . For example, (equal? '(this is a list) '(this is a list))  is true, but (equal? '(this is a list) '(this (is a) list))   is false. To be more precise, we can define equal?  recursively in terms of the basic eq? equality of symbols by saying that a and b are equal?  if they are both symbols and the symbols are eq?, or if they are both lists such that (car a)  is equal?  to (car b)  and (cdr a)  is equal?  to (cdr b) . Using this idea, implement equal?  as a procedure.36 Exer cise 2.55.   Eva Lu Ator types to the interpreter the expression (car ”abracadabra)   To her surprise, the interpreter prints back quote . Explain. 2.3.2  Example: Symbolic Differ entiation As an illustration of symbol manipulation and a further illustration of data abstraction, consider the design of a procedure that performs symbolic differentiation of algebraic expressions. We would like the procedure to take as ar guments an algebraic expression and a variable and to return th


key: chroma:document
string_value:
e derivative of the expression with respect to the variable. For example, if the arguments to the procedure are ax2 + bx + c and x, the procedure should return 2 ax + b. Symbolic dif ferentiation is of special historical significance in Lisp. It was one of the motivating examples behind the development of a computer language for symbol manipulation. Furthermore, it marked the beginning of the line of research that led to the development of powerful systems for symbolic mathematical work, which are currently being used by a growing number of applied mathematicians and physicists. In developing the symbolic-dif ferentiation program, we will follow the same strategy of data abstraction that we followed in developing the rational-number system of section 2.1.1 . That is, we will first define a differentiation algorithm that operates on abstract objects such as “sums,” “products,” and “variables” without worrying about how these are to be represented. Only afterward will we address the representation problem.The d


key: chroma:document
string_value:
iffer entiation pr ogram with abstract data In order to keep things simple, we will consider a very simple symbolic- differentiation program that handles expressions that are built up using only the operations of addition and multiplication with two ar guments. Differentiation of any such expression can be carried out by applying the following reduction rules: Observe that the latter two rules are recursive in nature. That is, to obtain the derivative of a sum we first find the derivatives of the terms and add them. Each of the terms may in turn be an expression that needs to be decomposed. Decomposing into smaller and smaller pieces will eventually produce pieces that are either constants or variables, whose derivatives will be either 0 or 1. To embody these rules in a procedure we indulge in a little wishful thinking, as we did in designing the rational-number implementation. If we had a means for representing algebraic expressions, we should be able to tell whether an expression is a sum, a product, a cons


key: chroma:document
string_value:
tant, or a variable. We should be able to extract the parts of an expression. For a sum, for example we want to be able to extract the addend (first term) and the augend (second term). We should also be able to construct expressions from parts. Let us assume that we already have procedures to implement the following selectors, constructors, and predicates: (variable? e) Is e a variable? (same-variable? v1 v2) Are v1 and v2 the same variable?(sum? e) Is e a sum? (addend e) Addend of the sum e. (augend e) Augend of the sum e. (make-sum a1 a2) Construct the sum of a1 and a2. (product? e)Is e a product? (multiplier e) Multiplier of the product e. (multiplicand e) Multiplicand of the product e. (make-product m1 m2) Construct the product of m1 and m2. Using these, and the primitive predicate number? , which identifies numbers, we can express the dif ferentiation rules as the following procedure: (define (deriv exp var)     (cond ((number? exp) 0)           ((variable? exp)            (if (same-variable? exp var) 1 


key: chroma:document
string_value:
0))           ((sum? exp)            (make-sum (deriv (addend exp) var)                      (deriv (augend exp) var)))           ((product? exp)            (make-sum              (make-product (multiplier exp)                            (deriv (multiplicand exp) var))              (make-product (deriv (multiplier exp) var)                            (multiplicand exp))))           (else            (error "unknown expression type -- DERIV" exp))))   This deriv  procedure incorporates the complete dif ferentiation algorithm. Since it is expressed in terms of abstract data, it will work no matter how we choose to represent algebraic expressions, as long as we design a proper set of selectors and constructors. This is the issue we must address next. Repr esenting algebraic expr essionsWe can imagine many ways to use list structure to represent algebraic expressions. For example, we could use lists of symbols that mirror the usual algebraic notation, representing ax + b as the list (a * x + b) . However , one esp


key: chroma:document
string_value:
ecially straightforward choice is to use the same parenthesized prefix notation that Lisp uses for combinations; that is, to represent ax + b as (+ (* a x) b) . Then our data representation for the differentiation problem is as follows: The variables are symbols. They are identified by the primitive predicate symbol? : (define (variable? x) (symbol? x))   Two variables are the same if the symbols representing them are eq?: (define (same-variable? v1 v2)     (and (variable? v1) (variable? v2) (eq? v1 v2)))   Sums and products are constructed as lists: (define (make-sum a1 a2) (list '+ a1 a2))     (define (make-product m1 m2) (list '* m1 m2))   A sum is a list whose first element is the symbol +: (define (sum? x)     (and (pair? x) (eq? (car x) '+)))   The addend is the second item of the sum list: (define (addend s) (cadr s))   The augend is the third item of the sum list: (define (augend s) (caddr s))   A product is a list whose first element is the symbol *:(define (product? x)     (and (pair? x) (eq? (car x


key: chroma:document
string_value:
) '*)))   The multiplier is the second item of the product list: (define (multiplier p) (cadr p))   The multiplicand is the third item of the product list: (define (multiplicand p) (caddr p))   Thus, we need only combine these with the algorithm as embodied by deriv  in order to have a working symbolic-dif ferentiation program. Let us look at some examples of its behavior: (deriv '(+ x 3) 'x)   (+ 1 0)  (deriv '(* x y) 'x)   (+ (* x 0) (* 1 y))   (deriv '(* (* x y) (+ x 3)) 'x)   (+ (* (* x y) (+ 1 0))      (* (+ (* x 0) (* 1 y))         (+  x 3)))   The program produces answers that are correct; however , they are unsimplified. It is true that but we would like the program to know that x · 0 = 0, 1 · y = y, and 0 + y = y. The answer for the second example should have been simply y. As the third example shows, this becomes a serious issue when the expressions are complex. Our dif ficulty is much like the one we encountered with the rational- number implementation: we haven't reduced answers to simplest form. 


key: chroma:document
string_value:
To accomplish the rational-number reduction, we needed to change only the constructors and the selectors of the implementation. We can adopt a similarstrategy here. We won't change deriv  at all. Instead, we will change make- sum so that if both summands are numbers, make-sum  will add them and return their sum. Also, if one of the summands is 0, then make-sum  will return the other summand. (define (make-sum a1 a2)     (cond ((=number? a1 0) a2)           ((=number? a2 0) a1)           ((and (number? a1) (number? a2)) (+ a1 a2))           (else (list '+ a1 a2))))   This uses the procedure =number? , which checks whether an expression is equal to a given number: (define (=number? exp num)     (and (number? exp) (= exp num)))   Similarly , we will change make-product  to build in the rules that 0 times anything is 0 and 1 times anything is the thing itself: (define (make-product m1 m2)     (cond ((or (=number? m1 0) (=number? m2 0)) 0)           ((=number? m1 1) m2)           ((=number? m2 1) m1)           ((a


key: chroma:document
string_value:
nd (number? m1) (number? m2)) (* m1 m2))           (else (list '* m1 m2))))   Here is how this version works on our three examples: (deriv '(+ x 3) 'x)   1  (deriv '(* x y) 'x)   y  (deriv '(* (* x y) (+ x 3)) 'x)   (+ (* x y) (* y (+ x 3)))   Although this is quite an improvement, the third example shows that there is still a long way to go before we get a program that puts expressions into aform that we might agree is “simplest.” The problem of algebraic simplification is complex because, among other reasons, a form that may be simplest for one purpose may not be for another . Exer cise 2.56.   Show how to extend the basic dif ferentiator to handle more kinds of expressions. For instance, implement the dif ferentiation rule by adding a new clause to the deriv  program and defining appropriate procedures exponentiation? , base , exponent , and make-exponentiation . (You may use the symbol ** to denote exponentiation.) Build in the rules that anything raised to the power 0 is 1 and anything raised to the powe


key: chroma:document
string_value:
r 1 is the thing itself. Exer cise 2.57.   Extend the dif ferentiation program to handle sums and products of arbitrary numbers of (two or more) terms. Then the last example above could be expressed as (deriv '(* x y (+ x 3)) 'x)   Try to do this by changing only the representation for sums and products, without changing the deriv  procedure at all. For example, the addend  of a sum would be the first term, and the augend  would be the sum of the rest of the terms. Exer cise 2.58.   Suppose we want to modify the dif ferentiation program so that it works with ordinary mathematical notation, in which + and * are infix rather than prefix operators. Since the dif ferentiation program is defined in terms of abstract data, we can modify it to work with dif ferent representations of expressions solely by changing the predicates, selectors, and constructors that define the representation of the algebraic expressions on which the dif ferentiator is to operate. a. Show how to do this in order to dif ferentiate algebrai


key: chroma:document
string_value:
c expressions presented in infix form, such as (x + (3 * (x + (y + 2)))) . To simplify the task, assume that + and * always take two ar guments and that expressions are fully parenthesized.b. The problem becomes substantially harder if we allow standard algebraic notation, such as (x + 3 * (x + y + 2)) , which drops unnecessary parentheses and assumes that multiplication is done before addition. Can you design appropriate predicates, selectors, and constructors for this notation such that our derivative program still works? 2.3.3  Example: Repr esenting Sets In the previous examples we built representations for two kinds of compound data objects: rational numbers and algebraic expressions. In one of these examples we had the choice of simplifying (reducing) the expressions at either construction time or selection time, but other than that the choice of a representation for these structures in terms of lists was straightforward. When we turn to the representation of sets, the choice of a representation is not 


key: chroma:document
string_value:
so obvious. Indeed, there are a number of possible representations, and they dif fer significantly from one another in several ways. Informally , a set is simply a collection of distinct objects. To give a more precise definition we can employ the method of data abstraction. That is, we define “set” by specifying the operations that are to be used on sets. These are union-set , intersection-set , element-of-set? , and adjoin- set. Element-of-set?  is a predicate that determines whether a given element is a member of a set. Adjoin-set  takes an object and a set as arguments and returns a set that contains the elements of the original set and also the adjoined element. Union-set  computes the union of two sets, which is the set containing each element that appears in either ar gument. Intersection-set  computes the intersection of two sets, which is the set containing only elements that appear in both ar guments. From the viewpoint of data abstraction, we are free to design any representation that implements th


key: chroma:document
string_value:
ese operations in a way consistent with the interpretations given above.37 Sets as unorder ed lists One way to represent a set is as a list of its elements in which no element appears more than once. The empty set is represented by the empty list. Inthis representation, element-of-set?  is similar to the procedure memq  of section 2.3.1 . It uses equal?  instead of eq? so that the set elements need not be symbols: (define (element-of-set? x set)     (cond ((null? set) false)           ((equal? x (car set)) true)           (else (element-of-set? x (cdr set)))))   Using this, we can write adjoin-set . If the object to be adjoined is already in the set, we just return the set. Otherwise, we use cons  to add the object to the list that represents the set: (define (adjoin-set x set)     (if (element-of-set? x set)         set         (cons x set)))   For intersection-set  we can use a recursive strategy . If we know how to form the intersection of set2  and the cdr of set1 , we only need to decide whether to inclu


key: chroma:document
string_value:
de the car of set1  in this. But this depends on whether (car set1)  is also in set2 . Here is the resulting procedure: (define (intersection-set set1 set2)     (cond ((or (null? set1) (null? set2)) '())           ((element-of-set? (car set1) set2)                   (cons (car set1)                  (intersection-set (cdr set1) set2)))           (else (intersection-set (cdr set1) set2))))   In designing a representation, one of the issues we should be concerned with is ef ficiency . Consider the number of steps required by our set operations. Since they all use element-of-set? , the speed of this operation has a major impact on the ef ficiency of the set implementation as a whole. Now , in order to check whether an object is a member of a set, element- of-set?  may have to scan the entire set. (In the worst case, the object turns out not to be in the set.) Hence, if the set has n elements, element-of-set? might take up to n steps. Thus, the number of steps required grows as θ( n).The number of steps required 


key: chroma:document
string_value:
by adjoin-set , which uses this operation, also grows as θ( n). For intersection-set , which does an element-of- set?  check for each element of set1 , the number of steps required grows as the product of the sizes of the sets involved, or θ( n2) for two sets of size n. The same will be true of union-set . Exer cise 2.59.   Implement the union-set  operation for the unordered-list representation of sets. Exer cise 2.60.   We specified that a set would be represented as a list with no duplicates. Now suppose we allow duplicates. For instance, the set {1,2,3} could be represented as the list (2 3 2 1 3 2 2) . Design procedures element-of-set? , adjoin-set , union-set , and intersection- set that operate on this representation. How does the ef ficiency of each compare with the corresponding procedure for the non-duplicate representation? Are there applications for which you would use this representation in preference to the non-duplicate one? Sets as order ed lists One way to speed up our set operations is to ch


key: chroma:document
string_value:
ange the representation so that the set elements are listed in increasing order . To do this, we need some way to compare two objects so that we can say which is bigger . For example, we could compare symbols lexicographically , or we could agree on some method for assigning a unique number to an object and then compare the elements by comparing the corresponding numbers. To keep our discussion simple, we will consider only the case where the set elements are numbers, so that we can compare elements using > and <. We will represent a set of numbers by listing its elements in increasing order . Whereas our first representation above allowed us to represent the set {1,3,6,10} by listing the elements in any order , our new representation allows only the list (1 3 6 10) . One advantage of ordering shows up in element-of-set? : In checking for the presence of an item, we no longer have to scan the entire set. If we reach a set element that is lar ger than the item we are looking for , then we know that the item is


key: chroma:document
string_value:
 not in the set:(define (element-of-set? x set)     (cond ((null? set) false)           ((= x (car set)) true)           ((< x (car set)) false)           (else (element-of-set? x (cdr set)))))   How many steps does this save? In the worst case, the item we are looking for may be the lar gest one in the set, so the number of steps is the same as for the unordered representation. On the other hand, if we search for items of many dif ferent sizes we can expect that sometimes we will be able to stop searching at a point near the beginning of the list and that other times we will still need to examine most of the list. On the average we should expect to have to examine about half of the items in the set. Thus, the average number of steps required will be about n/2. This is still θ( n) growth, but it does save us, on the average, a factor of 2 in number of steps over the previous implementation. We obtain a more impressive speedup with intersection-set . In the unordered representation this operation required θ( n


key: chroma:document
string_value:
2) steps, because we performed a complete scan of set2  for each element of set1 . But with the ordered representation, we can use a more clever method. Begin by comparing the initial elements, x1 and x2, of the two sets. If x1 equals x2, then that gives an element of the intersection, and the rest of the intersection is the intersection of the cdrs of the two sets. Suppose, however , that x1 is less than x2. Since x2 is the smallest element in set2 , we can immediately conclude that x1 cannot appear anywhere in set2  and hence is not in the intersection. Hence, the intersection is equal to the intersection of set2  with the cdr of set1 . Similarly , if x2 is less than x1, then the intersection is given by the intersection of set1  with the cdr of set2 . Here is the procedure: (define (intersection-set set1 set2)     (if (or (null? set1) (null? set2))         '()            (let ((x1 (car set1)) (x2 (car set2)))           (cond ((= x1 x2)                  (cons x1                        (intersection-set (cdr


key: chroma:document
string_value:
 set1)                                         (cdr set2))))                 ((< x1 x2)                  (intersection-set (cdr set1) set2))                 ((< x2 x1)                  (intersection-set set1 (cdr set2)))))))   To estimate the number of steps required by this process, observe that at each step we reduce the intersection problem to computing intersections of smaller sets – removing the first element from set1  or set2  or both. Thus, the number of steps required is at most the sum of the sizes of set1  and set2 , rather than the product of the sizes as with the unordered representation. This is θ( n) growth rather than θ( n2) – a considerable speedup, even for sets of moderate size. Exer cise 2.61.   Give an implementation of adjoin-set  using the ordered representation. By analogy with element-of-set?  show how to take advantage of the ordering to produce a procedure that requires on the average about half as many steps as with the unordered representation. Exer cise 2.62.   Give a θ( n) imple


key: chroma:document
string_value:
mentation of union-set  for sets represented as ordered lists. Sets as binary tr ees We can do better than the ordered-list representation by arranging the set elements in the form of a tree. Each node of the tree holds one element of the set, called the “entry” at that node, and a link to each of two other (possibly empty) nodes. The “left” link points to elements smaller than the one at the node, and the “right” link to elements greater than the one at the node. Figure 2.16 shows some trees that represent the set {1,3,5,7,9,1 1}. The same set may be represented by a tree in a number of dif ferent ways. The only thing we require for a valid representation is that all elements in the left subtree be smaller than the node entry and that all elements in the right subtree be lar ger. Figur e 2.16:   Various binary trees that represent the set { 1,3,5,7,9,1 1 }.The advantage of the tree representation is this: Suppose we want to check whether a number x is contained in a set. We begin by comparing x with the entr


key: chroma:document
string_value:
y in the top node. If x is less than this, we know that we need only search the left subtree; if x is greater , we need only search the right subtree. Now , if the tree is “balanced,” each of these subtrees will be about half the size of the original. Thus, in one step we have reduced the problem of searching a tree of size n to searching a tree of size n/2. Since the size of the tree is halved at each step, we should expect that the number of steps needed to search a tree of size n grows as θ( log n).38 For lar ge sets, this will be a significant speedup over the previous representations. We can represent trees by using lists. Each node will be a list of three items: the entry at the node, the left subtree, and the right subtree. A left or a right subtree of the empty list will indicate that there is no subtree connected there. We can describe this representation by the following procedures:39 (define (entry tree) (car tree))   (define (left-branch tree) (cadr tree))   (define (right-branch tree) (caddr tree


key: chroma:document
string_value:
))   (define (make-tree entry left right)     (list entry left right))   Now we can write the element-of-set?  procedure using the strategy described above: (define (element-of-set? x set)     (cond ((null? set) false)           ((= x (entry set)) true)          ((< x (entry set))            (element-of-set? x (left-branch set)))           ((> x (entry set))            (element-of-set? x (right-branch set)))))   Adjoining an item to a set is implemented similarly and also requires θ( log n) steps. To adjoin an item x, we compare x with the node entry to determine whether x should be added to the right or to the left branch, and having adjoined x to the appropriate branch we piece this newly constructed branch together with the original entry and the other branch. If x is equal to the entry , we just return the node. If we are asked to adjoin x to an empty tree, we generate a tree that has x as the entry and empty right and left branches. Here is the procedure: (define (adjoin-set x set)     (cond ((null? set)


key: chroma:document
string_value:
 (make-tree x '() '()))           ((= x (entry set)) set)           ((< x (entry set))            (make-tree (entry set)                       (adjoin-set x (left-branch set))                       (right-branch set)))           ((> x (entry set))            (make-tree (entry set)                       (left-branch set)                       (adjoin-set x (right-branch set))))))   The above claim that searching the tree can be performed in a logarithmic number of steps rests on the assumption that the tree is “balanced,” i.e., that the left and the right subtree of every tree have approximately the same number of elements, so that each subtree contains about half the elements of its parent. But how can we be certain that the trees we construct will be balanced? Even if we start with a balanced tree, adding elements with adjoin-set  may produce an unbalanced result. Since the position of a newly adjoined element depends on how the element compares with the items already in the set, we can expect that if we add


key: chroma:document
string_value:
 elements “randomly” the tree will tend to be balanced on the average. But this is not a guarantee. For example, if we start with an empty set and adjoin the numbers 1through 7 in sequence we end up with the highly unbalanced tree shown in figure 2.17. In this tree all the left subtrees are empty , so it has no advantage over a simple ordered list. One way to solve this problem is to define an operation that transforms an arbitrary tree into a balanced tree with the same elements. Then we can perform this transformation after every few adjoin-set  operations to keep our set in balance. There are also other ways to solve this problem, most of which involve designing new data structures for which searching and insertion both can be done in θ( log n) steps.40 Figur e 2.17:   Unbalanced tree produced by adjoining 1 through 7 in sequence. Exer cise 2.63.   Each of the following two procedures converts a binary tree to a list. (define (tree->list-1 tree)     (if (null? tree)         '()         (append (tree->list-


key: chroma:document
string_value:
1 (left-branch tree))                 (cons (entry tree)                       (tree->list-1 (right-branch tree))))))   (define (tree->list-2 tree)     (define (copy-to-list tree result-list)       (if (null? tree)           result-list           (copy-to-list (left-branch tree)                         (cons (entry tree)                               (copy-to-list (right-branch tree)                                            result-list)))))     (copy-to-list tree '()))   a. Do the two procedures produce the same result for every tree? If not, how do the results dif fer? What lists do the two procedures produce for the trees in figure 2.16? b. Do the two procedures have the same order of growth in the number of steps required to convert a balanced tree with n elements to a list? If not, which one grows more slowly? Exer cise 2.64.   The following procedure list->tree  converts an ordered list to a balanced binary tree. The helper procedure partial-tree  takes as arguments an integer n and list of at least n 


key: chroma:document
string_value:
elements and constructs a balanced tree containing the first n elements of the list. The result returned by partial-tree  is a pair (formed with cons ) whose car is the constructed tree and whose cdr is the list of elements not included in the tree. (define (list->tree elements)     (car (partial-tree elements (length elements))))     (define (partial-tree elts n)     (if (= n 0)         (cons '() elts)         (let ((left-size (quotient (- n 1) 2)))           (let ((left-result (partial-tree elts left-size)))             (let ((left-tree (car left-result))                   (non-left-elts (cdr left-result))                   (right-size (- n (+ left-size 1))))               (let ((this-entry (car non-left-elts))                     (right-result (partial-tree (cdr non-left- elts)                                                right-size)))                 (let ((right-tree (car right-result))                       (remaining-elts (cdr right-result)))                   (cons (make-tree this-entry left-tree ri


key: chroma:document
string_value:
ght- tree)                        remaining-elts))))))))  a. Write a short paragraph explaining as clearly as you can how partial- tree  works. Draw the tree produced by list->tree  for the list (1 3 5 7 9 11). b. What is the order of growth in the number of steps required by list- >tree  to convert a list of n elements? Exer cise 2.65.   Use the results of exercises 2.63 and  2.64 to give θ( n) implementations of union-set  and intersection-set  for sets implemented as (balanced) binary trees.41 Sets and information r etrieval We have examined options for using lists to represent sets and have seen how the choice of representation for a data object can have a lar ge impact on the performance of the programs that use the data. Another reason for concentrating on sets is that the techniques discussed here appear again and again in applications involving information retrieval. Consider a data base containing a lar ge number of individual records, such as the personnel files for a company or the transactions in 


key: chroma:document
string_value:
an accounting system. A typical data-management system spends a lar ge amount of time accessing or modifying the data in the records and therefore requires an efficient method for accessing records. This is done by identifying a part of each record to serve as an identifying key. A key can be anything that uniquely identifies the record. For a personnel file, it might be an employee's ID number . For an accounting system, it might be a transaction number . Whatever the key is, when we define the record as a data structure we should include a key selector procedure that retrieves the key associated with a given record. Now we represent the data base as a set of records. T o locate the record with a given key we use a procedure lookup , which takes as ar guments a key and a data base and which returns the record that has that key , or false if there is no such record. Lookup  is implemented in almost the same way as element-of-set? . For example, if the set of records is implemented as an unordered list, we cou


key: chroma:document
string_value:
ld use(define (lookup given-key set-of-records)     (cond ((null? set-of-records) false)           ((equal? given-key (key (car set-of-records)))            (car set-of-records))           (else (lookup given-key (cdr set-of-records)))))   Of course, there are better ways to represent lar ge sets than as unordered lists. Information-retrieval systems in which records have to be “randomly accessed” are typically implemented by a tree-based method, such as the binary-tree representation discussed previously . In designing such a system the methodology of data abstraction can be a great help. The designer can create an initial implementation using a simple, straightforward representation such as unordered lists. This will be unsuitable for the eventual system, but it can be useful in providing a “quick and dirty” data base with which to test the rest of the system. Later on, the data representation can be modified to be more sophisticated. If the data base is accessed in terms of abstract selectors and construct


key: chroma:document
string_value:
ors, this change in representation will not require any changes to the rest of the system. Exer cise 2.66.   Implement the lookup  procedure for the case where the set of records is structured as a binary tree, ordered by the numerical values of the keys. 2.3.4  Example: Huffman Encoding T rees This section provides practice in the use of list structure and data abstraction to manipulate sets and trees. The application is to methods for representing data as sequences of ones and zeros (bits). For example, the ASCII standard code used to represent text in computers encodes each character as a sequence of seven bits. Using seven bits allows us to distinguish 27, or 128, possible dif ferent characters. In general, if we want to distinguish n different symbols, we will need to use log2n bits per symbol. If all our messages are made up of the eight symbols A, B, C, D, E, F, G, and H, we can choose a code with three bits per character , for example A 000 C 010 E 100 G 110 B 001 D 01 1F 101 H 111With this code, the 


key: chroma:document
string_value:
message BACADAEAF ABBAAAGAH is encoded as the string of 54 bits 00100001000001 10001000001010000010010000000001 100001 11 Codes such as ASCII and the A-through-H code above are known as fixed- length  codes, because they represent each symbol in the message with the same number of bits. It is sometimes advantageous to use variable-length codes, in which dif ferent symbols may be represented by dif ferent numbers of bits. For example, Morse code does not use the same number of dots and dashes for each letter of the alphabet. In particular , E, the most frequent letter , is represented by a single dot. In general, if our messages are such that some symbols appear very frequently and some very rarely , we can encode data more ef ficiently (i.e., using fewer bits per message) if we assign shorter codes to the frequent symbols. Consider the following alternative code for the letters A through H: A 0 C 1010 E 1100G 1110 B 100 D 101 1F 1101H 1111 With this code, the same message as above is encoded as the string 100


key: chroma:document
string_value:
010100101 10110001 10101001000001 11001 111 This string contains 42 bits, so it saves more than 20% in space in comparison with the fixed-length code shown above. One of the dif ficulties of using a variable-length code is knowing when you have reached the end of a symbol in reading a sequence of zeros and ones. Morse code solves this problem by using a special separator code  (in this case, a pause) after the sequence of dots and dashes for each letter . Another solution is to design the code in such a way that no complete code for any symbol is the beginning (or prefix) of the code for another symbol. Such a code is called a prefix code . In the example above, A is encoded by 0 and B is encoded by 100, so no other symbol can have a code that begins with 0 or with 100.In general, we can attain significant savings if we use variable-length prefix codes that take advantage of the relative frequencies of the symbols in the messages to be encoded. One particular scheme for doing this is called the Huffman encodi


key: chroma:document
string_value:
ng method, after its discoverer , David Huf fman. A Huf fman code can be represented as a binary tree whose leaves are the symbols that are encoded. At each non-leaf node of the tree there is a set containing all the symbols in the leaves that lie below the node. In addition, each symbol at a leaf is assigned a weight (which is its relative frequency), and each non- leaf node contains a weight that is the sum of all the weights of the leaves lying below it. The weights are not used in the encoding or the decoding process. We will see below how they are used to help construct the tree. Figur e 2.18:   A Huf fman encoding tree. Figure 2.18 shows the Huf fman tree for the A-through-H code given above. The weights at the leaves indicate that the tree was designed for messages in which A appears with relative frequency 8, B with relative frequency 3, and the other letters each with relative frequency 1. Given a Huf fman tree, we can find the encoding of any symbol by starting at the root and moving down until we r


key: chroma:document
string_value:
each the leaf that holds the symbol. Each time we move down a left branch we add a 0 to the code, and each time we move down a right branch we add a 1. (We decide which branch to follow by testing to see which branch either is the leaf node for the symbol or contains the symbol in its set.) For example, starting from the root of thetree in figure 2.18, we arrive at the leaf for D by following a right branch, then a left branch, then a right branch, then a right branch; hence, the code for D is 101 1. To decode a bit sequence using a Huf fman tree, we begin at the root and use the successive zeros and ones of the bit sequence to determine whether to move down the left or the right branch. Each time we come to a leaf, we have generated a new symbol in the message, at which point we start over from the root of the tree to find the next symbol. For example, suppose we are given the tree above and the sequence 10001010. Starting at the root, we move down the right branch, (since the first bit of the string is 1), 


key: chroma:document
string_value:
then down the left branch (since the second bit is 0), then down the left branch (since the third bit is also 0). This brings us to the leaf for B, so the first symbol of the decoded message is B. Now we start again at the root, and we make a left move because the next bit in the string is 0. This brings us to the leaf for A. Then we start again at the root with the rest of the string 1010, so we move right, left, right, left and reach C. Thus, the entire message is BAC. Generating Huffman tr ees Given an “alphabet” of symbols and their relative frequencies, how do we construct the “best” code? (In other words, which tree will encode messages with the fewest bits?) Huffman gave an algorithm for doing this and showed that the resulting code is indeed the best variable-length code for messages where the relative frequency of the symbols matches the frequencies with which the code was constructed. We will not prove this optimality of Huffman codes here, but we will show how Huf fman trees are constructed.42 The 


key: chroma:document
string_value:
algorithm for generating a Huf fman tree is very simple. The idea is to arrange the tree so that the symbols with the lowest frequency appear farthest away from the root. Begin with the set of leaf nodes, containing symbols and their frequencies, as determined by the initial data from which the code is to be constructed. Now find two leaves with the lowest weights and mer ge them to produce a node that has these two nodes as its left and right branches. The weight of the new node is the sum of the two weights.Remove the two leaves from the original set and replace them by this new node. Now continue this process. At each step, mer ge two nodes with the smallest weights, removing them from the set and replacing them with a node that has these two as its left and right branches. The process stops when there is only one node left, which is the root of the entire tree. Here is how the Huf fman tree of figure 2.18 was generated: Initial leaves {(A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1)} Merge{(A 8) (B 3) ({C


key: chroma:document
string_value:
 D} 2) (E 1) (F 1) (G 1) (H 1)} Merge{(A 8) (B 3) ({C D} 2) ({E F} 2) (G 1) (H 1)} Merge{(A 8) (B 3) ({C D} 2) ({E F} 2) ({G H} 2)} Merge{(A 8) (B 3) ({C D} 2) ({E F G H} 4)} Merge{(A 8) ({B C D} 5) ({E F G H} 4)} Merge{(A 8) ({B C D E F G H} 9)} Final mer ge{({A B C D E F G H} 17)} The algorithm does not always specify a unique tree, because there may not be unique smallest-weight nodes at each step. Also, the choice of the order in which the two nodes are mer ged (i.e., which will be the right branch and which will be the left branch) is arbitrary . Repr esenting Huffman tr eesIn the exercises below we will work with a system that uses Huf fman trees to encode and decode messages and generates Huf fman trees according to the algorithm outlined above. We will begin by discussing how trees are represented. Leaves of the tree are represented by a list consisting of the symbol leaf , the symbol at the leaf, and the weight: (define (make-leaf symbol weight)     (list 'leaf symbol weight))   (define (leaf? object


key: chroma:document
string_value:
)     (eq? (car object) 'leaf))   (define (symbol-leaf x) (cadr x))   (define (weight-leaf x) (caddr x))   A general tree will be a list of a left branch, a right branch, a set of symbols, and a weight. The set of symbols will be simply a list of the symbols, rather than some more sophisticated set representation. When we make a tree by merging two nodes, we obtain the weight of the tree as the sum of the weights of the nodes, and the set of symbols as the union of the sets of symbols for the nodes. Since our symbol sets are represented as lists, we can form the union by using the append  procedure we defined in section 2.2.1 : (define (make-code-tree left right)     (list left           right           (append (symbols left) (symbols right))           (+ (weight left) (weight right))))   If we make a tree in this way , we have the following selectors: (define (left-branch tree) (car tree))     (define (right-branch tree) (cadr tree))   (define (symbols tree)     (if (leaf? tree)         (list (symbol-leaf tr


key: chroma:document
string_value:
ee))        (caddr tree)))   (define (weight tree)     (if (leaf? tree)         (weight-leaf tree)         (cadddr tree)))   The procedures symbols  and weight  must do something slightly dif ferent depending on whether they are called with a leaf or a general tree. These are simple examples of generic pr ocedur es (procedures that can handle more than one kind of data), which we will have much more to say about in sections 2.4 and 2.5. The decoding pr ocedur e The following procedure implements the decoding algorithm. It takes as arguments a list of zeros and ones, together with a Huf fman tree. (define (decode bits tree)     (define (decode-1 bits current-branch)       (if (null? bits)           '()           (let ((next-branch                  (choose-branch (car bits) current-branch)))             (if (leaf? next-branch)                 (cons (symbol-leaf next-branch)                       (decode-1 (cdr bits) tree))                 (decode-1 (cdr bits) next-branch)))))     (decode-1 bits tree))   (define


key: chroma:document
string_value:
 (choose-branch bit branch)     (cond ((= bit 0) (left-branch branch))           ((= bit 1) (right-branch branch))           (else (error "bad bit -- CHOOSE-BRANCH" bit))))   The procedure decode-1  takes two ar guments: the list of remaining bits and the current position in the tree. It keeps moving “down” the tree, choosing a left or a right branch according to whether the next bit in the list is a zero or a one. (This is done with the procedure choose-branch .) When it reaches aleaf, it returns the symbol at that leaf as the next symbol in the message by cons ing it onto the result of decoding the rest of the message, starting at the root of the tree. Note the error check in the final clause of choose-branch , which complains if the procedure finds something other than a zero or a one in the input data. Sets of weighted elements In our representation of trees, each non-leaf node contains a set of symbols, which we have represented as a simple list. However , the tree-generating algorithm discussed above re


key: chroma:document
string_value:
quires that we also work with sets of leaves and trees, successively mer ging the two smallest items. Since we will be required to repeatedly find the smallest item in a set, it is convenient to use an ordered representation for this kind of set. We will represent a set of leaves and trees as a list of elements, arranged in increasing order of weight. The following adjoin-set  procedure for constructing sets is similar to the one described in exercise 2.61; however , items are compared by their weights, and the element being added to the set is never already in it. (define (adjoin-set x set)     (cond ((null? set) (list x))           ((< (weight x) (weight (car set))) (cons x set))           (else (cons (car set)                       (adjoin-set x (cdr set))))))   The following procedure takes a list of symbol-frequency pairs such as ((A 4) (B 2) (C 1) (D 1))  and constructs an initial ordered set of leaves, ready to be mer ged according to the Huf fman algorithm: (define (make-leaf-set pairs)     (if (null?


key: chroma:document
string_value:
 pairs)         '()         (let ((pair (car pairs)))           (adjoin-set (make-leaf (car pair)    ; symbol                                  (cadr pair))  ; frequency                       (make-leaf-set (cdr pairs))))))  Exer cise 2.67.   Define an encoding tree and a sample message: (define sample-tree     (make-code-tree (make-leaf 'A 4)                     (make-code-tree                      (make-leaf 'B 2)                      (make-code-tree (make-leaf 'D 1)                                      (make-leaf 'C 1)))))     (define sample-message '(0 1 1 0 0 1 0 1 0 1 1 1 0))   Use the decode  procedure to decode the message, and give the result. Exer cise 2.68.   The encode  procedure takes as ar guments a message and a tree and produces the list of bits that gives the encoded message. (define (encode message tree)     (if (null? message)         '()         (append (encode-symbol (car message) tree)                 (encode (cdr message) tree))))   Encode-symbol  is a procedure, which you must write, th


key: chroma:document
string_value:
at returns the list of bits that encodes a given symbol according to a given tree. You should design encode-symbol  so that it signals an error if the symbol is not in the tree at all. Test your procedure by encoding the result you obtained in exercise 2.67 with the sample tree and seeing whether it is the same as the original sample message. Exer cise 2.69.   The following procedure takes as its ar gument a list of symbol-frequency pairs (where no symbol appears in more than one pair) and generates a Huf fman encoding tree according to the Huf fman algorithm. (define (generate-huffman-tree pairs)     (successive-merge (make-leaf-set pairs)))  Make-leaf-set  is the procedure given above that transforms the list of pairs into an ordered set of leaves. Successive-merge  is the procedure you must write, using make-code-tree  to successively mer ge the smallest-weight elements of the set until there is only one element left, which is the desired Huffman tree. (This procedure is slightly tricky , but not really co


key: chroma:document
string_value:
mplicated. If you find yourself designing a complex procedure, then you are almost certainly doing something wrong. You can take significant advantage of the fact that we are using an ordered set representation.) Exer cise 2.70.   The following eight-symbol alphabet with associated relative frequencies was designed to ef ficiently encode the lyrics of 1950s rock songs. (Note that the “symbols” of an “alphabet” need not be individual letters.) A 2NA 16 BOOM 1SHA 3 GET 2YIP 9 JOB 2WAH1 Use generate-huffman-tree  (exercise 2.69) to generate a corresponding Huffman tree, and use encode  (exercise 2.68) to encode the following message: Get a job Sha na na na na na na na na Get a job Sha na na na na na na na na Wah yip yip yip yip yip yip yip yip yip Sha boom How many bits are required for the encoding? What is the smallest number of bits that would be needed to encode this song if we used a fixed-length code for the eight-symbol alphabet?Exer cise 2.71.   Suppose we have a Huf fman tree for an alphabet of n symbol


key: chroma:document
string_value:
s, and that the relative frequencies of the symbols are 1, 2, 4, ..., 2n- 1. Sketch the tree for n=5; for n=10. In such a tree (for general n) how may bits are required to encode the most frequent symbol? the least frequent symbol? Exer cise 2.72.   Consider the encoding procedure that you designed in exercise 2.68. What is the order of growth in the number of steps needed to encode a symbol? Be sure to include the number of steps needed to search the symbol list at each node encountered. To answer this question in general is dif ficult. Consider the special case where the relative frequencies of the n symbols are as described in exercise 2.71, and give the order of growth (as a function of n) of the number of steps needed to encode the most frequent and least frequent symbols in the alphabet. 32 Allowing quotation in a language wreaks havoc with the ability to reason about the language in simple terms, because it destroys the notion that equals can be substituted for equals. For example, three is one plus tw


key: chroma:document
string_value:
o, but the word “three” is not the phrase “one plus two.” Quotation is powerful because it gives us a way to build expressions that manipulate other expressions (as we will see when we write an interpreter in chapter 4). But allowing statements in a language that talk about other statements in that language makes it very dif ficult to maintain any coherent principle of what “equals can be substituted for equals” should mean. For example, if we know that the evening star is the morning star , then from the statement “the evening star is V enus” we can deduce “the morning star is Venus.” However , given that “John knows that the evening star is V enus” we cannot infer that “John knows that the morning star is V enus.” 33 The single quote is dif ferent from the double quote we have been using to enclose character strings to be printed. Whereas the single quote can be used to denote lists or symbols, the double quote is used only with character strings. In this book, the only use for character strings is as items


key: chroma:document
string_value:
 to be printed. 34 Strictly , our use of the quotation mark violates the general rule that all compound expressions in our language should be delimited by parentheses and look like lists. We can recover this consistency by introducing a special form quote , which serves the same purpose as the quotation mark. Thus, we would type (quote a)  instead of 'a, and we would type (quote (a b c))  instead of '(a b c) . This is precisely how the interpreter works. The quotation mark is just a single-character abbreviation for wrapping the next complete expression with quote  to form (quote < expression >). This is important because it maintains the principle that any expression seen by the interpreter can be manipulated as a data object. For instance, we could construct the expression (car '(a b c)) , which is the same as (car (quote (a b c))) , by evaluating (list 'car (list 'quote '(a b c))) . 35 We can consider two symbols to be “the same” if they consist of the same characters in the same order . Such a definition 


key: chroma:document
string_value:
skirts a deep issue that we are not yet ready to address: the meaning of“sameness” in a programming language. We will return to this in chapter 3 (section 3.1.3 ). 36 In practice, programmers use equal?  to compare lists that contain numbers as well as symbols. Numbers are not considered to be symbols. The question of whether two numerically equal numbers (as tested by =) are also eq? is highly implementation-dependent. A better definition of equal?  (such as the one that comes as a primitive in Scheme) would also stipulate that if a and b are both numbers, then a and b are equal?  if they are numerically equal. 37 If we want to be more formal, we can specify “consistent with the interpretations given above” to mean that the operations satisfy a collection of rules such as these: For any set S and any object x, (element-of-set? x (adjoin-set x S))  is true (informally: “Adjoining an object to a set produces a set that contains the object”). For any sets S and T and any object x, (element-of-set? x (union-set 


key: chroma:document
string_value:
S T))  is equal to (or (element-of-set? x S) (element-of-set? x T))  (informally: “The elements of (union S T)  are the elements that are in S or in T”). For any object x, (element-of-set? x '())  is false (informally: “No object is an element of the empty set”). 38 Halving the size of the problem at each step is the distinguishing characteristic of logarithmic growth, as we saw with the fast-exponentiation algorithm of section 1.2.4  and the half-interval search method of section 1.3.3 . 39 We are representing sets in terms of trees, and trees in terms of lists – in ef fect, a data abstraction built upon a data abstraction. We can regard the procedures entry , left-branch , right-branch , and make-tree  as a way of isolating the abstraction of a “binary tree” from the particular way we might wish to represent such a tree in terms of list structure. 40 Examples of such structures include B-trees and red-black tr ees. There is a lar ge literature on data structures devoted to this problem. See Cormen, Leiserso


key: chroma:document
string_value:
n, and Rivest 1990. 41 Exercises 2.63-2.65 are due to Paul Hilfinger . 42 See Hamming 1980 for a discussion of the mathematical properties of Huf fman codes.2.4  Multiple Representations for Abstract Data We have introduced data abstraction, a methodology for structuring systems in such a way that much of a program can be specified independent of the choices involved in implementing the data objects that the program manipulates. For example, we saw in section 2.1.1  how to separate the task of designing a program that uses rational numbers from the task of implementing rational numbers in terms of the computer language's primitive mechanisms for constructing compound data. The key idea was to erect an abstraction barrier – in this case, the selectors and constructors for rational numbers ( make-rat , numer , denom ) – that isolates the way rational numbers are used from their underlying representation in terms of list structure. A similar abstraction barrier isolates the details of the procedures that perform


key: chroma:document
string_value:
 rational arithmetic ( add-rat , sub-rat , mul-rat , and div-rat ) from the “higher -level” procedures that use rational numbers. The resulting program has the structure shown in figure 2.1. These data-abstraction barriers are powerful tools for controlling complexity . By isolating the underlying representations of data objects, we can divide the task of designing a lar ge program into smaller tasks that can be performed separately . But this kind of data abstraction is not yet powerful enough, because it may not always make sense to speak of “the underlying representation” for a data object. For one thing, there might be more than one useful representation for a data object, and we might like to design systems that can deal with multiple representations. To take a simple example, complex numbers may be represented in two almost equivalent ways: in rectangular form (real and imaginary parts) and in polar form (magnitude and angle). Sometimes rectangular form is more appropriate and sometimes polar form is mo


key: chroma:document
string_value:
re appropriate. Indeed, it is perfectly plausible to imagine a system in which complex numbers are represented in both ways, and in which the procedures for manipulating complex numbers work with either representation.More importantly , programming systems are often designed by many people working over extended periods of time, subject to requirements that change over time. In such an environment, it is simply not possible for everyone to agree in advance on choices of data representation. So in addition to the data-abstraction barriers that isolate representation from use, we need abstraction barriers that isolate dif ferent design choices from each other and permit dif ferent choices to coexist in a single program. Furthermore, since lar ge programs are often created by combining pre- existing modules that were designed in isolation, we need conventions that permit programmers to incorporate modules into lar ger systems additively , that is, without having to redesign or reimplement these modules. In this s


key: chroma:document
string_value:
ection, we will learn how to cope with data that may be represented in dif ferent ways by dif ferent parts of a program. This requires constructing generic pr ocedur es – procedures that can operate on data that may be represented in more than one way . Our main technique for building generic procedures will be to work in terms of data objects that have type tags , that is, data objects that include explicit information about how they are to be processed. We will also discuss data-dir ected  programming, a powerful and convenient implementation strategy for additively assembling systems with generic operations. We begin with the simple complex-number example. W e will see how type tags and data-directed style enable us to design separate rectangular and polar representations for complex numbers while maintaining the notion of an abstract “complex-number” data object. We will accomplish this by defining arithmetic procedures for complex numbers ( add-complex , sub- complex , mul-complex , and div-complex ) in 


key: chroma:document
string_value:
terms of generic selectors that access parts of a complex number independent of how the number is represented. The resulting complex-number system, as shown in figure 2.19, contains two dif ferent kinds of abstraction barriers. The “horizontal” abstraction barriers play the same role as the ones in figure 2.1. They isolate “higher -level” operations from “lower -level” representations. In addition, there is a “vertical” barrier that gives us the ability to separately design and install alternative representations. Figur e 2.19:   Data-abstraction barriers in the complex-number system.In section 2.5 we will show how to use type tags and data-directed style to develop a generic arithmetic package. This provides procedures ( add, mul, and so on) that can be used to manipulate all sorts of “numbers” and can be easily extended when a new kind of number is needed. In section 2.5.3 , we'll show how to use generic arithmetic in a system that performs symbolic algebra. 2.4.1  Repr esentations for Complex Numbers We wi


key: chroma:document
string_value:
ll develop a system that performs arithmetic operations on complex numbers as a simple but unrealistic example of a program that uses generic operations. We begin by discussing two plausible representations for complex numbers as ordered pairs: rectangular form (real part and imaginary part) and polar form (magnitude and angle).43 Section 2.4.2  will show how both representations can be made to coexist in a single system through the use of type tags and generic operations. Like rational numbers, complex numbers are naturally represented as ordered pairs. The set of complex numbers can be thought of as a two- dimensional space with two orthogonal axes, the “real” axis and the “imaginary” axis. (See figure 2.20.) From this point of view , the complex number z = x + iy (where i2 = - 1) can be thought of as the point in the plane whose real coordinate is x and whose imaginary coordinate is y. Addition of complex numbers reduces in this representation to addition of coordinates: When multiplying complex numbers, i


key: chroma:document
string_value:
t is more natural to think in terms of representing a complex number in polar form, as a magnitude and an angle (r and A in figure 2.20). The product of two complex numbers is the vector obtained by stretching one complex number by the length of the other and then rotating it through the angle of the other: Thus, there are two dif ferent representations for complex numbers, which are appropriate for dif ferent operations. Yet, from the viewpoint of someone writing a program that uses complex numbers, the principle of data abstraction suggests that all the operations for manipulating complex numbers should be available regardless of which representation is used by the computer . For example, it is often useful to be able to find the magnitude of a complex number that is specified by rectangular coordinates. Similarly , it is often useful to be able to determine the real part of a complex number that is specified by polar coordinates. Figur e 2.20:   Complex numbers as points in the plane. To design such a syst


key: chroma:document
string_value:
em, we can follow the same data-abstraction strategy we followed in designing the rational-number package in section 2.1.1 . Assume that the operations on complex numbers are implemented in terms of four selectors: real-part , imag-part , magnitude , and angle . Also assume that we have two procedures for constructing complex numbers: make-from-real-imag  returns a complex number with specified real and imaginary parts, and make-from-mag-ang  returns a complex number withspecified magnitude and angle. These procedures have the property that, for any complex number z, both (make-from-real-imag (real-part z) (imag-part z))   and (make-from-mag-ang (magnitude z) (angle z))   produce complex numbers that are equal to z. Using these constructors and selectors, we can implement arithmetic on complex numbers using the “abstract data” specified by the constructors and selectors, just as we did for rational numbers in section 2.1.1 . As shown in the formulas above, we can add and subtract complex numbers in terms of r


key: chroma:document
string_value:
eal and imaginary parts while multiplying and dividing complex numbers in terms of magnitudes and angles: (define (add-complex z1 z2)     (make-from-real-imag (+ (real-part z1) (real-part z2))                          (+ (imag-part z1) (imag-part z2))))   (define (sub-complex z1 z2)     (make-from-real-imag (- (real-part z1) (real-part z2))                          (- (imag-part z1) (imag-part z2))))   (define (mul-complex z1 z2)     (make-from-mag-ang (* (magnitude z1) (magnitude z2))                        (+ (angle z1) (angle z2))))   (define (div-complex z1 z2)     (make-from-mag-ang (/ (magnitude z1) (magnitude z2))                        (- (angle z1) (angle z2))))   To complete the complex-number package, we must choose a representation and we must implement the constructors and selectors in terms of primitive numbers and primitive list structure. There are two obvious ways to do this: We can represent a complex number in “rectangular form” as a pair (real part, imaginary part) or in “polar form” as a 


key: chroma:document
string_value:
pair (magnitude, angle). Which shall we choose?In order to make the dif ferent choices concrete, imagine that there are two programmers, Ben Bitdiddle and Alyssa P . Hacker , who are independently designing representations for the complex-number system. Ben chooses to represent complex numbers in rectangular form. With this choice, selecting the real and imaginary parts of a complex number is straightforward, as is constructing a complex number with given real and imaginary parts. To find the magnitude and the angle, or to construct a complex number with a given magnitude and angle, he uses the trigonometric relations which relate the real and imaginary parts ( x, y) to the magnitude and the angle ( r, A).44 Ben's representation is therefore given by the following selectors and constructors: (define (real-part z) (car z))   (define (imag-part z) (cdr z))   (define (magnitude z)     (sqrt (+ (square (real-part z)) (square (imag-part z)))))   (define (angle z)     (atan (imag-part z) (real-part z)))   (define (


key: chroma:document
string_value:
make-from-real-imag x y) (cons x y))   (define (make-from-mag-ang r a)     (cons (* r (cos a)) (* r (sin a))))   Alyssa, in contrast, chooses to represent complex numbers in polar form. For her , selecting the magnitude and angle is straightforward, but she has to use the trigonometric relations to obtain the real and imaginary parts. Alyssa's representation is: (define (real-part z)     (* (magnitude z) (cos (angle z))))   (define (imag-part z)     (* (magnitude z) (sin (angle z))))   (define (magnitude z) (car z))   (define (angle z) (cdr z))  (define (make-from-real-imag x y)     (cons (sqrt (+ (square x) (square y)))           (atan y x)))   (define (make-from-mag-ang r a) (cons r a))   The discipline of data abstraction ensures that the same implementation of add-complex , sub-complex , mul-complex , and div-complex  will work with either Ben's representation or Alyssa's representation. 2.4.2  Tagged data One way to view data abstraction is as an application of the “principle of least commitment.” In imp


key: chroma:document
string_value:
lementing the complex-number system in section 2.4.1 , we can use either Ben's rectangular representation or Alyssa's polar representation. The abstraction barrier formed by the selectors and constructors permits us to defer to the last possible moment the choice of a concrete representation for our data objects and thus retain maximum flexibility in our system design. The principle of least commitment can be carried to even further extremes. If we desire, we can maintain the ambiguity of representation even after  we have designed the selectors and constructors, and elect to use both Ben's representation and Alyssa's representation. If both representations are included in a single system, however , we will need some way to distinguish data in polar form from data in rectangular form. Otherwise, if we were asked, for instance, to find the magnitude  of the pair (3,4), we wouldn't know whether to answer 5 (interpreting the number in rectangular form) or 3 (interpreting the number in polar form). A straightforw


key: chroma:document
string_value:
ard way to accomplish this distinction is to include a type tag  – the symbol rectangular  or polar  – as part of each complex number . Then when we need to manipulate a complex number we can use the tag to decide which selector to apply . In order to manipulate tagged data, we will assume that we have procedures type-tag  and contents  that extract from a data object the tag and the actual contents (the polar or rectangular coordinates, in the case of a complex number). We will also postulate a procedure attach-tag  that takesa tag and contents and produces a tagged data object. A straightforward way to implement this is to use ordinary list structure: (define (attach-tag type-tag contents)     (cons type-tag contents))   (define (type-tag datum)     (if (pair? datum)         (car datum)         (error "Bad tagged datum -- TYPE-TAG" datum)))   (define (contents datum)     (if (pair? datum)         (cdr datum)         (error "Bad tagged datum -- CONTENTS" datum)))   Using these procedures, we can define predi


key: chroma:document
string_value:
cates rectangular?  and polar? , which recognize polar and rectangular numbers, respectively: (define (rectangular? z)     (eq? (type-tag z) 'rectangular))   (define (polar? z)     (eq? (type-tag z) 'polar))   With type tags, Ben and Alyssa can now modify their code so that their two different representations can coexist in the same system. Whenever Ben constructs a complex number , he tags it as rectangular . Whenever Alyssa constructs a complex number , she tags it as polar . In addition, Ben and Alyssa must make sure that the names of their procedures do not conflict. One way to do this is for Ben to append the suf fix rectangular  to the name of each of his representation procedures and for Alyssa to append polar  to the names of hers. Here is Ben's revised rectangular representation from section 2.4.1 : (define (real-part-rectangular z) (car z))   (define (imag-part-rectangular z) (cdr z))   (define (magnitude-rectangular z)     (sqrt (+ (square (real-part-rectangular z))              (square (imag-part-


key: chroma:document
string_value:
rectangular z)))))   (define (angle-rectangular z)    (atan (imag-part-rectangular z)           (real-part-rectangular z)))   (define (make-from-real-imag-rectangular x y)     (attach-tag 'rectangular (cons x y)))   (define (make-from-mag-ang-rectangular r a)     (attach-tag 'rectangular                 (cons (* r (cos a)) (* r (sin a)))))   and here is Alyssa's revised polar representation: (define (real-part-polar z)     (* (magnitude-polar z) (cos (angle-polar z))))   (define (imag-part-polar z)     (* (magnitude-polar z) (sin (angle-polar z))))   (define (magnitude-polar z) (car z))   (define (angle-polar z) (cdr z))   (define (make-from-real-imag-polar x y)     (attach-tag 'polar                  (cons (sqrt (+ (square x) (square y)))                        (atan y x))))   (define (make-from-mag-ang-polar r a)     (attach-tag 'polar (cons r a)))   Each generic selector is implemented as a procedure that checks the tag of its ar gument and calls the appropriate procedure for handling data of that type. Fo


key: chroma:document
string_value:
r example, to obtain the real part of a complex number , real-part examines the tag to determine whether to use Ben's real-part- rectangular  or Alyssa's real-part-polar . In either case, we use contents to extract the bare, untagged datum and send this to the rectangular or polar procedure as required: (define (real-part z)     (cond ((rectangular? z)            (real-part-rectangular (contents z)))           ((polar? z)            (real-part-polar (contents z)))           (else (error "Unknown type -- REAL-PART" z))))   (define (imag-part z)    (cond ((rectangular? z)            (imag-part-rectangular (contents z)))           ((polar? z)            (imag-part-polar (contents z)))           (else (error "Unknown type -- IMAG-PART" z))))   (define (magnitude z)     (cond ((rectangular? z)            (magnitude-rectangular (contents z)))           ((polar? z)            (magnitude-polar (contents z)))           (else (error "Unknown type -- MAGNITUDE" z))))   (define (angle z)     (cond ((rectangular? z)      


key: chroma:document
string_value:
      (angle-rectangular (contents z)))           ((polar? z)            (angle-polar (contents z)))           (else (error "Unknown type -- ANGLE" z))))   To implement the complex-number arithmetic operations, we can use the same procedures add-complex , sub-complex , mul-complex , and div- complex  from section 2.4.1 , because the selectors they call are generic, and so will work with either representation. For example, the procedure add- complex  is still (define (add-complex z1 z2)     (make-from-real-imag (+ (real-part z1) (real-part z2))                          (+ (imag-part z1) (imag-part z2))))   Finally , we must choose whether to construct complex numbers using Ben's representation or Alyssa's representation. One reasonable choice is to construct rectangular numbers whenever we have real and imaginary parts and to construct polar numbers whenever we have magnitudes and angles: (define (make-from-real-imag x y)     (make-from-real-imag-rectangular x y))   (define (make-from-mag-ang r a)     (make-fr


key: chroma:document
string_value:
om-mag-ang-polar r a))  Figur e 2.21:   Structure of the generic complex-arithmetic system. The resulting complex-number system has the structure shown in figure 2.21. The system has been decomposed into three relatively independent parts: the complex-number -arithmetic operations, Alyssa's polar implementation, and Ben's rectangular implementation. The polar and rectangular implementations could have been written by Ben and Alyssa working separately , and both of these can be used as underlying representations by a third programmer implementing the complex- arithmetic procedures in terms of the abstract constructor/selector interface. Since each data object is tagged with its type, the selectors operate on the data in a generic manner . That is, each selector is defined to have a behavior that depends upon the particular type of data it is applied to. Notice the general mechanism for interfacing the separate representations: W ithin a given representation implementation (say , Alyssa's polar package) a compl


key: chroma:document
string_value:
ex number is an untyped pair (magnitude, angle). When a generic selector operates on a number of polar  type, it strips of f the tag and passes the contents on to Alyssa's code. Conversely , when Alyssa constructs a number for general use, she tags it with a type so that it can be appropriately recognized by the higher -level procedures. This discipline of stripping of f and attaching tags as data objects are passed from level to level can be an important or ganizational strategy , as we shall see in section 2.5. 2.4.3  Data-Dir ected Pr ogramming and Additivity The general strategy of checking the type of a datum and calling an appropriate procedure is called dispatching on type . This is a powerful strategy for obtaining modularity in system design. On the other hand,implementing the dispatch as in section 2.4.2  has two significant weaknesses. One weakness is that the generic interface procedures ( real- part , imag-part , magnitude , and angle ) must know about all the dif ferent representations. For inst


key: chroma:document
string_value:
ance, suppose we wanted to incorporate a new representation for complex numbers into our complex-number system. We would need to identify this new representation with a type, and then add a clause to each of the generic interface procedures to check for the new type and apply the appropriate selector for that representation. Another weakness of the technique is that even though the individual representations can be designed separately , we must guarantee that no two procedures in the entire system have the same name. This is why Ben and Alyssa had to change the names of their original procedures from section 2.4.1 . The issue underlying both of these weaknesses is that the technique for implementing generic interfaces is not additive . The person implementing the generic selector procedures must modify those procedures each time a new representation is installed, and the people interfacing the individual representations must modify their code to avoid name conflicts. In each of these cases, the changes that m


key: chroma:document
string_value:
ust be made to the code are straightforward, but they must be made nonetheless, and this is a source of inconvenience and error . This is not much of a problem for the complex-number system as it stands, but suppose there were not two but hundreds of dif ferent representations for complex numbers. And suppose that there were many generic selectors to be maintained in the abstract-data interface. Suppose, in fact, that no one programmer knew all the interface procedures or all the representations. The problem is real and must be addressed in such programs as lar ge-scale data-base-management systems. What we need is a means for modularizing the system design even further . This is provided by the programming technique known as data-dir ected programming . To understand how data-directed programming works, begin with the observation that whenever we deal with a set of generic operations that are common to a set of dif ferent types we are, in ef fect, dealing with a two-dimensional table that contains the possib


key: chroma:document
string_value:
le operations on one axis and the possible types on the other axis. The entries in the table are theprocedures that implement each operation for each type of ar gument presented. In the complex-number system developed in the previous section, the correspondence between operation name, data type, and actual procedure was spread out among the various conditional clauses in the generic interface procedures. But the same information could have been organized in a table, as shown in figure 2.22. Data-directed programming is the technique of designing programs to work with such a table directly . Previously , we implemented the mechanism that interfaces the complex-arithmetic code with the two representation packages as a set of procedures that each perform an explicit dispatch on type. Here we will implement the interface as a single procedure that looks up the combination of the operation name and ar gument type in the table to find the correct procedure to apply , and then applies it to the contents of the argum


key: chroma:document
string_value:
ent. If we do this, then to add a new representation package to the system we need not change any existing procedures; we need only add new entries to the table. Figur e 2.22:   Table of operations for the complex-number system. To implement this plan, assume that we have two procedures, put and get, for manipulating the operation-and-type table: (put <op> <type> <item>)  installs the <item> in the table, indexed by the <op> and the <type>. (get <op> <type>)  looks up the <op>, <type> entry in the table and returns the item found there. If no item is found, get returns false.For now , we can assume that put and get are included in our language. In chapter 3 (section 3.3.3 , exercise 3.24) we will see how to implement these and other operations for manipulating tables. Here is how data-directed programming can be used in the complex-number system. Ben, who developed the rectangular representation, implements his code just as he did originally . He defines a collection of procedures, or a package , and interfac


key: chroma:document
string_value:
es these to the rest of the system by adding entries to the table that tell the system how to operate on rectangular numbers. This is accomplished by calling the following procedure: (define (install-rectangular-package)     ;; internal procedures     (define (real-part z) (car z))     (define (imag-part z) (cdr z))     (define (make-from-real-imag x y) (cons x y))     (define (magnitude z)       (sqrt (+ (square (real-part z))                (square (imag-part z)))))     (define (angle z)       (atan (imag-part z) (real-part z)))     (define (make-from-mag-ang r a)       (cons (* r (cos a)) (* r (sin a))))     ;; interface to the rest of the system     (define (tag x) (attach-tag 'rectangular x))     (put 'real-part '(rectangular) real-part)     (put 'imag-part '(rectangular) imag-part)     (put 'magnitude '(rectangular) magnitude)     (put 'angle '(rectangular) angle)     (put 'make-from-real-imag 'rectangular          (lambda (x y) (tag (make-from-real-imag x y))))     (put 'make-from-mag-ang 'rectangular 


key: chroma:document
string_value:
         (lambda (r a) (tag (make-from-mag-ang r a))))     'done)   Notice that the internal procedures here are the same procedures from section 2.4.1  that Ben wrote when he was working in isolation. No changesare necessary in order to interface them to the rest of the system. Moreover , since these procedure definitions are internal to the installation procedure, Ben needn't worry about name conflicts with other procedures outside the rectangular package. To interface these to the rest of the system, Ben installs his real-part  procedure under the operation name real-part  and the type (rectangular) , and similarly for the other selectors.45 The interface also defines the constructors to be used by the external system.46 These are identical to Ben's internally defined constructors, except that they attach the tag. Alyssa's polar package is analogous: (define (install-polar-package)     ;; internal procedures     (define (magnitude z) (car z))     (define (angle z) (cdr z))     (define (make-from-mag-ang r 


key: chroma:document
string_value:
a) (cons r a))     (define (real-part z)       (* (magnitude z) (cos (angle z))))     (define (imag-part z)       (* (magnitude z) (sin (angle z))))     (define (make-from-real-imag x y)       (cons (sqrt (+ (square x) (square y)))             (atan y x)))     ;; interface to the rest of the system     (define (tag x) (attach-tag 'polar x))     (put 'real-part '(polar) real-part)     (put 'imag-part '(polar) imag-part)     (put 'magnitude '(polar) magnitude)     (put 'angle '(polar) angle)     (put 'make-from-real-imag 'polar          (lambda (x y) (tag (make-from-real-imag x y))))     (put 'make-from-mag-ang 'polar          (lambda (r a) (tag (make-from-mag-ang r a))))     'done)  Even though Ben and Alyssa both still use their original procedures defined with the same names as each other's (e.g., real-part ), these definitions are now internal to dif ferent procedures (see section 1.1.8 ), so there is no name conflict. The complex-arithmetic selectors access the table by means of a general “operation” proce


key: chroma:document
string_value:
dure called apply-generic , which applies a generic operation to some ar guments. Apply-generic  looks in the table under the name of the operation and the types of the ar guments and applies the resulting procedure if one is present:47 (define (apply-generic op . args)     (let ((type-tags (map type-tag args)))       (let ((proc (get op type-tags)))         (if proc             (apply proc (map contents args))             (error               "No method for these types -- APPLY-GENERIC"               (list op type-tags))))))   Using apply-generic , we can define our generic selectors as follows: (define (real-part z) (apply-generic 'real-part z))   (define (imag-part z) (apply-generic 'imag-part z))   (define (magnitude z) (apply-generic 'magnitude z))   (define (angle z) (apply-generic 'angle z))   Observe that these do not change at all if a new representation is added to the system. We can also extract from the table the constructors to be used by the programs external to the packages in making complex nu


key: chroma:document
string_value:
mbers from real and imaginary parts and from magnitudes and angles. As in section 2.4.2 , we construct rectangular numbers whenever we have real and imaginary parts, and polar numbers whenever we have magnitudes and angles: (define (make-from-real-imag x y)     ((get 'make-from-real-imag 'rectangular) x y))  (define (make-from-mag-ang r a)     ((get 'make-from-mag-ang 'polar) r a))   Exer cise 2.73.   Section 2.3.2  described a program that performs symbolic differentiation: (define (deriv exp var)     (cond ((number? exp) 0)           ((variable? exp) (if (same-variable? exp var) 1 0))           ((sum? exp)            (make-sum (deriv (addend exp) var)                      (deriv (augend exp) var)))           ((product? exp)            (make-sum              (make-product (multiplier exp)                            (deriv (multiplicand exp) var))              (make-product (deriv (multiplier exp) var)                            (multiplicand exp))))           < more rules can be added here >          (else (


key: chroma:document
string_value:
error "unknown expression type -- DERIV" exp))))   We can regard this program as performing a dispatch on the type of the expression to be dif ferentiated. In this situation the “type tag” of the datum is the algebraic operator symbol (such as +) and the operation being performed is deriv . We can transform this program into data-directed style by rewriting the basic derivative procedure as (define (deriv exp var)      (cond ((number? exp) 0)            ((variable? exp) (if (same-variable? exp var) 1 0))            (else ((get 'deriv (operator exp)) (operands exp)                                               var))))   (define (operator exp) (car exp))   (define (operands exp) (cdr exp))   a.  Explain what was done above. Why can't we assimilate the predicates number?  and same-variable?  into the data-directed dispatch?b.  Write the procedures for derivatives of sums and products, and the auxiliary code required to install them in the table used by the program above. c.  Choose any additional dif ferentiatio


key: chroma:document
string_value:
n rule that you like, such as the one for exponents (exercise 2.56), and install it in this data-directed system. d.  In this simple algebraic manipulator the type of an expression is the algebraic operator that binds it together . Suppose, however , we indexed the procedures in the opposite way , so that the dispatch line in deriv  looked like ((get (operator exp) 'deriv) (operands exp) var)   What corresponding changes to the derivative system are required? Exer cise 2.74.   Insatiable Enterprises, Inc., is a highly decentralized conglomerate company consisting of a lar ge number of independent divisions located all over the world. The company's computer facilities have just been interconnected by means of a clever network-interfacing scheme that makes the entire network appear to any user to be a single computer . Insatiable's president, in her first attempt to exploit the ability of the network to extract administrative information from division files, is dismayed to discover that, although all the divisi


key: chroma:document
string_value:
on files have been implemented as data structures in Scheme, the particular data structure used varies from division to division. A meeting of division managers is hastily called to search for a strategy to integrate the files that will satisfy headquarters' needs while preserving the existing autonomy of the divisions. Show how such a strategy can be implemented with data-directed programming. As an example, suppose that each division's personnel records consist of a single file, which contains a set of records keyed on employees' names. The structure of the set varies from division to division. Furthermore, each employee's record is itself a set (structured dif ferently from division to division) that contains information keyed under identifiers such as address  and salary . In particular:a.  Implement for headquarters a get-record  procedure that retrieves a specified employee's record from a specified personnel file. The procedure should be applicable to any division's file. Explain how the individual div


key: chroma:document
string_value:
isions' files should be structured. In particular , what type information must be supplied? b.  Implement for headquarters a get-salary  procedure that returns the salary information from a given employee's record from any division's personnel file. How should the record be structured in order to make this operation work? c.  Implement for headquarters a find-employee-record  procedure. This should search all the divisions' files for the record of a given employee and return the record. Assume that this procedure takes as ar guments an employee's name and a list of all the divisions' files. d.  When Insatiable takes over a new company , what changes must be made in order to incorporate the new personnel information into the central system? Message passing The key idea of data-directed programming is to handle generic operations in programs by dealing explicitly with operation-and-type tables, such as the table in figure 2.22. The style of programming we used in section 2.4.2 organized the required dispatching


key: chroma:document
string_value:
 on type by having each operation take care of its own dispatching. In effect, this decomposes the operation-and- type table into rows, with each generic operation procedure representing a row of the table. An alternative implementation strategy is to decompose the table into columns and, instead of using “intelligent operations” that dispatch on data types, to work with “intelligent data objects” that dispatch on operation names. We can do this by arranging things so that a data object, such as a rectangular number , is represented as a procedure that takes as input the required operation name and performs the operation indicated. In such a discipline, make-from-real-imag  could be written as(define (make-from-real-imag x y)     (define (dispatch op)       (cond ((eq? op 'real-part) x)             ((eq? op 'imag-part) y)             ((eq? op 'magnitude)              (sqrt (+ (square x) (square y))))             ((eq? op 'angle) (atan y x))             (else              (error "Unknown op -- MAKE-FROM-REAL-I


key: chroma:document
string_value:
MAG" op))))     dispatch)   The corresponding apply-generic  procedure, which applies a generic operation to an ar gument, now simply feeds the operation's name to the data object and lets the object do the work:48 (define (apply-generic op arg) (arg op))   Note that the value returned by make-from-real-imag  is a procedure – the internal dispatch  procedure. This is the procedure that is invoked when apply-generic  requests an operation to be performed. This style of programming is called message passing . The name comes from the image that a data object is an entity that receives the requested operation name as a “message.” We have already seen an example of message passing in section 2.1.3 , where we saw how cons , car, and cdr could be defined with no data objects but only procedures. Here we see that message passing is not a mathematical trick but a useful technique for organizing systems with generic operations. In the remainder of this chapter we will continue to use data-directed programming, rather t


key: chroma:document
string_value:
han message passing, to discuss generic arithmetic operations. In chapter 3 we will return to message passing, and we will see that it can be a powerful tool for structuring simulation programs. Exer cise 2.75.   Implement the constructor make-from-mag-ang  in message- passing style. This procedure should be analogous to the make-from-real- imag  procedure given above.Exer cise 2.76.   As a lar ge system with generic operations evolves, new types of data objects or new operations may be needed. For each of the three strategies – generic operations with explicit dispatch, data-directed style, and message-passing-style – describe the changes that must be made to a system in order to add new types or new operations. Which organization would be most appropriate for a system in which new types must often be added? Which would be most appropriate for a system in which new operations must often be added? 43 In actual computational systems, rectangular form is preferable to polar form most of the time because of roun


key: chroma:document
string_value:
dof f errors in conversion between rectangular and polar form. This is why the complex-number example is unrealistic. Nevertheless, it provides a clear illustration of the design of a system using generic operations and a good introduction to the more substantial systems to be developed later in this chapter . 44 The arctangent function referred to here, computed by Scheme's atan  procedure, is defined so as to take two ar guments y and x and to return the angle whose tangent is y/x. The signs of the ar guments determine the quadrant of the angle. 45 We use the list (rectangular)  rather than the symbol rectangular  to allow for the possibility of operations with multiple ar guments, not all of the same type. 46 The type the constructors are installed under needn't be a list because a constructor is always used to make an object of one particular type. 47 Apply-generic  uses the dotted-tail notation described in exercise 2.20, because dif ferent generic operations may take dif ferent numbers of ar guments. In


key: chroma:document
string_value:
 apply-generic , op has as its value the first argument to apply-generic  and args  has as its value a list of the remaining ar guments. Apply-generic  also uses the primitive procedure apply , which takes two ar guments, a procedure and a list. Apply  applies the procedure, using the elements in the list as ar guments. For example, (apply + (list 1 2 3 4))   returns 10. 48 One limitation of this or ganization is it permits only generic procedures of one ar gument.2.5  Systems with Generic Operations In the previous section, we saw how to design systems in which data objects can be represented in more than one way . The key idea is to link the code that specifies the data operations to the several representations by means of generic interface procedures. Now we will see how to use this same idea not only to define operations that are generic over dif ferent representations but also to define operations that are generic over dif ferent kinds of ar guments. We have already seen several dif ferent packages of ar


key: chroma:document
string_value:
ithmetic operations: the primitive arithmetic ( +, -, *, /) built into our language, the rational-number arithmetic ( add-rat , sub-rat , mul-rat , div- rat) of section 2.1.1 , and the complex-number arithmetic that we implemented in section 2.4.3 . We will now use data-directed techniques to construct a package of arithmetic operations that incorporates all the arithmetic packages we have already constructed. Figure 2.23 shows the structure of the system we shall build. Notice the abstraction barriers. From the perspective of someone using “numbers,” there is a single procedure add that operates on whatever numbers are supplied. Add is part of a generic interface that allows the separate ordinary- arithmetic, rational-arithmetic, and complex-arithmetic packages to be accessed uniformly by programs that use numbers. Any individual arithmetic package (such as the complex package) may itself be accessed through generic procedures (such as add-complex ) that combine packages designed for dif ferent representatio


key: chroma:document
string_value:
ns (such as rectangular and polar). Moreover , the structure of the system is additive, so that one can design the individual arithmetic packages separately and combine them to produce a generic arithmetic system. Figur e 2.23:   Generic arithmetic system.2.5.1  Generic Arithmetic Operations The task of designing generic arithmetic operations is analogous to that of designing the generic complex-number operations. We would like, for instance, to have a generic addition procedure add that acts like ordinary primitive addition + on ordinary numbers, like add-rat  on rational numbers, and like add-complex  on complex numbers. We can implement add, and the other generic arithmetic operations, by following the same strategy we used in section 2.4.3  to implement the generic selectors for complex numbers. We will attach a type tag to each kind of number and cause the generic procedure to dispatch to an appropriate package according to the data type of its ar guments. The generic arithmetic procedures are defined as


key: chroma:document
string_value:
 follows: (define (add x y) (apply-generic 'add x y))   (define (sub x y) (apply-generic 'sub x y))   (define (mul x y) (apply-generic 'mul x y))   (define (div x y) (apply-generic 'div x y))   We begin by installing a package for handling ordinary  numbers, that is, the primitive numbers of our language. We will tag these with the symbol scheme-number . The arithmetic operations in this package are the primitive arithmetic procedures (so there is no need to define extra procedures to handle the untagged numbers). Since these operations each take twoarguments, they are installed in the table keyed by the list (scheme-number scheme-number) : (define (install-scheme-number-package)     (define (tag x)       (attach-tag 'scheme-number x))        (put 'add '(scheme-number scheme-number)          (lambda (x y) (tag (+ x y))))     (put 'sub '(scheme-number scheme-number)          (lambda (x y) (tag (- x y))))     (put 'mul '(scheme-number scheme-number)          (lambda (x y) (tag (* x y))))     (put 'div '(scheme-


key: chroma:document
string_value:
number scheme-number)          (lambda (x y) (tag (/ x y))))     (put 'make 'scheme-number          (lambda (x) (tag x)))     'done)   Users of the Scheme-number package will create (tagged) ordinary numbers by means of the procedure: (define (make-scheme-number n)     ((get 'make 'scheme-number) n))   Now that the framework of the generic arithmetic system is in place, we can readily include new kinds of numbers. Here is a package that performs rational arithmetic. Notice that, as a benefit of additivity , we can use without modification the rational-number code from section 2.1.1  as the internal procedures in the package: (define (install-rational-package)     ;; internal procedures     (define (numer x) (car x))     (define (denom x) (cdr x))     (define (make-rat n d)       (let ((g (gcd n d)))         (cons (/ n g) (/ d g))))     (define (add-rat x y)      (make-rat (+ (* (numer x) (denom y))                    (* (numer y) (denom x)))                 (* (denom x) (denom y))))     (define (sub-rat x y) 


key: chroma:document
string_value:
      (make-rat (- (* (numer x) (denom y))                    (* (numer y) (denom x)))                 (* (denom x) (denom y))))     (define (mul-rat x y)       (make-rat (* (numer x) (numer y))                 (* (denom x) (denom y))))     (define (div-rat x y)       (make-rat (* (numer x) (denom y))                 (* (denom x) (numer y))))     ;; interface to rest of the system     (define (tag x) (attach-tag 'rational x))     (put 'add '(rational rational)          (lambda (x y) (tag (add-rat x y))))     (put 'sub '(rational rational)          (lambda (x y) (tag (sub-rat x y))))     (put 'mul '(rational rational)          (lambda (x y) (tag (mul-rat x y))))     (put 'div '(rational rational)          (lambda (x y) (tag (div-rat x y))))       (put 'make 'rational          (lambda (n d) (tag (make-rat n d))))     'done)   (define (make-rational n d)     ((get 'make 'rational) n d))   We can install a similar package to handle complex numbers, using the tag complex . In creating the package, we extract from 


key: chroma:document
string_value:
the table the operations make-from-real-imag  and make-from-mag-ang  that were defined by the rectangular and polar packages. Additivity permits us to use, as the internal operations, the same add-complex , sub-complex , mul-complex , and div- complex  procedures from section 2.4.1 .(define (install-complex-package)     ;; imported procedures from rectangular and polar packages     (define (make-from-real-imag x y)       ((get 'make-from-real-imag 'rectangular) x y))     (define (make-from-mag-ang r a)       ((get 'make-from-mag-ang 'polar) r a))     ;; internal procedures     (define (add-complex z1 z2)       (make-from-real-imag (+ (real-part z1) (real-part z2))                            (+ (imag-part z1) (imag-part z2))))     (define (sub-complex z1 z2)       (make-from-real-imag (- (real-part z1) (real-part z2))                            (- (imag-part z1) (imag-part z2))))     (define (mul-complex z1 z2)       (make-from-mag-ang (* (magnitude z1) (magnitude z2))                          (+ (angle z1) (a


key: chroma:document
string_value:
ngle z2))))     (define (div-complex z1 z2)       (make-from-mag-ang (/ (magnitude z1) (magnitude z2))                          (- (angle z1) (angle z2))))     ;; interface to rest of the system     (define (tag z) (attach-tag 'complex z))     (put 'add '(complex complex)          (lambda (z1 z2) (tag (add-complex z1 z2))))     (put 'sub '(complex complex)          (lambda (z1 z2) (tag (sub-complex z1 z2))))     (put 'mul '(complex complex)          (lambda (z1 z2) (tag (mul-complex z1 z2))))     (put 'div '(complex complex)          (lambda (z1 z2) (tag (div-complex z1 z2))))     (put 'make-from-real-imag 'complex          (lambda (x y) (tag (make-from-real-imag x y))))     (put 'make-from-mag-ang 'complex          (lambda (r a) (tag (make-from-mag-ang r a))))     'done)   Programs outside the complex-number package can construct complex numbers either from real and imaginary parts or from magnitudes andangles. Notice how the underlying procedures, originally defined in the rectangular and polar packages, ar


key: chroma:document
string_value:
e exported to the complex package, and exported from there to the outside world. (define (make-complex-from-real-imag x y)     ((get 'make-from-real-imag 'complex) x y))   (define (make-complex-from-mag-ang r a)     ((get 'make-from-mag-ang 'complex) r a))   What we have here is a two-level tag system. A typical complex number , such as 3 + 4 i in rectangular form, would be represented as shown in figure 2.24. The outer tag ( complex ) is used to direct the number to the complex package. Once within the complex package, the next tag (rectangular ) is used to direct the number to the rectangular package. In a large and complicated system there might be many levels, each interfaced with the next by means of generic operations. As a data object is passed “downward,” the outer tag that is used to direct it to the appropriate package is stripped of f (by applying contents ) and the next level of tag (if any) becomes visible to be used for further dispatching. Figur e 2.24:   Representation of 3 + 4 i in rectangula


key: chroma:document
string_value:
r form. In the above packages, we used add-rat , add-complex , and the other arithmetic procedures exactly as originally written. Once these definitions are internal to dif ferent installation procedures, however , they no longer need names that are distinct from each other: we could simply name them add, sub, mul, and div in both packages. Exer cise 2.77.   Louis Reasoner tries to evaluate the expression (magnitude z) where z is the object shown in figure 2.24. To his surprise, instead of the answer 5 he gets an error message from apply-generic , saying there is no method for the operation magnitude  on the types (complex) . He shows this interaction to Alyssa P . Hacker , who says “The problem is that the complex- number selectors were never defined for complex  numbers, just for polarand rectangular  numbers. All you have to do to make this work is add the following to the complex  package:” (put 'real-part '(complex) real-part)   (put 'imag-part '(complex) imag-part)   (put 'magnitude '(complex) magnitude


key: chroma:document
string_value:
)   (put 'angle '(complex) angle)   Describe in detail why this works. As an example, trace through all the procedures called in evaluating the expression (magnitude z)  where z is the object shown in figure 2.24. In particular , how many times is apply- generic  invoked? What procedure is dispatched to in each case? Exer cise 2.78.   The internal procedures in the scheme-number  package are essentially nothing more than calls to the primitive procedures +, -, etc. It was not possible to use the primitives of the language directly because our type-tag system requires that each data object have a type attached to it. In fact, however , all Lisp implementations do have a type system, which they use internally . Primitive predicates such as symbol?  and number?  determine whether data objects have particular types. Modify the definitions of type- tag, contents , and attach-tag  from section 2.4.2  so that our generic system takes advantage of Scheme's internal type system. That is to say , the system should work


key: chroma:document
string_value:
 as before except that ordinary numbers should be represented simply as Scheme numbers rather than as pairs whose car is the symbol scheme-number . Exer cise 2.79.   Define a generic equality predicate equ?  that tests the equality of two numbers, and install it in the generic arithmetic package. This operation should work for ordinary numbers, rational numbers, and complex numbers. Exer cise 2.80.   Define a generic predicate =zero?  that tests if its ar gument is zero, and install it in the generic arithmetic package. This operation should work for ordinary numbers, rational numbers, and complex numbers. 2.5.2  Combining Data of Differ ent T ypesWe have seen how to define a unified arithmetic system that encompasses ordinary numbers, complex numbers, rational numbers, and any other type of number we might decide to invent, but we have ignored an important issue. The operations we have defined so far treat the dif ferent data types as being completely independent. Thus, there are separate packages for adding


key: chroma:document
string_value:
, say , two ordinary numbers, or two complex numbers. What we have not yet considered is the fact that it is meaningful to define operations that cross the type boundaries, such as the addition of a complex number to an ordinary number . We have gone to great pains to introduce barriers between parts of our programs so that they can be developed and understood separately . We would like to introduce the cross-type operations in some carefully controlled way , so that we can support them without seriously violating our module boundaries. One way to handle cross-type operations is to design a dif ferent procedure for each possible combination of types for which the operation is valid. For example, we could extend the complex-number package so that it provides a procedure for adding complex numbers to ordinary numbers and installs this in the table using the tag (complex scheme-number) :49 ;; to be included in the complex package   (define (add-complex-to-schemenum z x)     (make-from-real-imag (+ (real-part z) 


key: chroma:document
string_value:
x)                          (imag-part z)))   (put 'add '(complex scheme-number)        (lambda (z x) (tag (add-complex-to-schemenum z x))))   This technique works, but it is cumbersome. With such a system, the cost of introducing a new type is not just the construction of the package of procedures for that type but also the construction and installation of the procedures that implement the cross-type operations. This can easily be much more code than is needed to define the operations on the type itself. The method also undermines our ability to combine separate packages additively , or least to limit the extent to which the implementors of the individual packages need to take account of other packages. For instance, in the example above, it seems reasonable that handling mixed operations on complex numbers and ordinary numbers should be the responsibility of thecomplex-number package. Combining rational numbers and complex numbers, however , might be done by the complex package, by the rational package, or 


key: chroma:document
string_value:
by some third package that uses operations extracted from these two packages. Formulating coherent policies on the division of responsibility among packages can be an overwhelming task in designing systems with many packages and many cross-type operations. Coer cion In the general situation of completely unrelated operations acting on completely unrelated types, implementing explicit cross-type operations, cumbersome though it may be, is the best that one can hope for . Fortunately , we can usually do better by taking advantage of additional structure that may be latent in our type system. Often the dif ferent data types are not completely independent, and there may be ways by which objects of one type may be viewed as being of another type. This process is called coercion. For example, if we are asked to arithmetically combine an ordinary number with a complex number , we can view the ordinary number as a complex number whose imaginary part is zero. This transforms the problem to that of combining two comple


key: chroma:document
string_value:
x numbers, which can be handled in the ordinary way by the complex-arithmetic package. In general, we can implement this idea by designing coercion procedures that transform an object of one type into an equivalent object of another type. Here is a typical coercion procedure, which transforms a given ordinary number to a complex number with that real part and zero imaginary part: (define (scheme-number->complex n)     (make-complex-from-real-imag (contents n) 0))   We install these coercion procedures in a special coercion table, indexed under the names of the two types: (put-coercion 'scheme-number 'complex scheme-number->complex)   (We assume that there are put-coercion  and get-coercion  procedures available for manipulating this table.) Generally some of the slots in thetable will be empty , because it is not generally possible to coerce an arbitrary data object of each type into all other types. For example, there is no way to coerce an arbitrary complex number to an ordinary number , so there will be no


key: chroma:document
string_value:
 general complex->scheme-number  procedure included in the table. Once the coercion table has been set up, we can handle coercion in a uniform manner by modifying the apply-generic  procedure of section 2.4.3 . When asked to apply an operation, we first check whether the operation is defined for the ar guments' types, just as before. If so, we dispatch to the procedure found in the operation-and-type table. Otherwise, we try coercion. For simplicity , we consider only the case where there are two ar guments.50 We check the coercion table to see if objects of the first type can be coerced to the second type. If so, we coerce the first ar gument and try the operation again. If objects of the first type cannot in general be coerced to the second type, we try the coercion the other way around to see if there is a way to coerce the second ar gument to the type of the first argument. Finally , if there is no known way to coerce either type to the other type, we give up. Here is the procedure: (define (apply-generic


key: chroma:document
string_value:
 op . args)     (let ((type-tags (map type-tag args)))       (let ((proc (get op type-tags)))         (if proc             (apply proc (map contents args))             (if (= (length args) 2)                 (let ((type1 (car type-tags))                       (type2 (cadr type-tags))                       (a1 (car args))                       (a2 (cadr args)))                   (let ((t1->t2 (get-coercion type1 type2))                         (t2->t1 (get-coercion type2 type1)))                     (cond (t1->t2                            (apply-generic op (t1->t2 a1) a2))                           (t2->t1                            (apply-generic op a1 (t2->t1 a2)))                           (else                           (error "No method for these types"                                   (list op type-tags))))))                 (error "No method for these types"                        (list op type-tags)))))))   This coercion scheme has many advantages over the method of defining explicit cross-type opera


key: chroma:document
string_value:
tions, as outlined above. Although we still need to write coercion procedures to relate the types (possibly n2 procedures for a system with n types), we need to write only one procedure for each pair of types rather than a dif ferent procedure for each collection of types and each generic operation.51 What we are counting on here is the fact that the appropriate transformation between types depends only on the types themselves, not on the operation to be applied. On the other hand, there may be applications for which our coercion scheme is not general enough. Even when neither of the objects to be combined can be converted to the type of the other it may still be possible to perform the operation by converting both objects to a third type. In order to deal with such complexity and still preserve modularity in our programs, it is usually necessary to build systems that take advantage of still further structure in the relations among types, as we discuss next. Hierar chies of types The coercion scheme presented


key: chroma:document
string_value:
 above relied on the existence of natural relations between pairs of types. Often there is more “global” structure in how the dif ferent types relate to each other . For instance, suppose we are building a generic arithmetic system to handle integers, rational numbers, real numbers, and complex numbers. In such a system, it is quite natural to regard an integer as a special kind of rational number , which is in turn a special kind of real number , which is in turn a special kind of complex number . What we actually have is a so-called hierar chy of types , in which, for example, integers are a subtype  of rational numbers (i.e., any operation that can be applied to a rational number can automatically be applied to an integer). Conversely , we say that rational numbers form a supertype  of integers. The particular hierarchy we have here is of a very simple kind, inwhich each type has at most one supertype and at most one subtype. Such a structure, called a tower , is illustrated in figure 2.25. Figur e 2.25:  


key: chroma:document
string_value:
 A tower of types. If we have a tower structure, then we can greatly simplify the problem of adding a new type to the hierarchy , for we need only specify how the new type is embedded in the next supertype above it and how it is the supertype of the type below it. For example, if we want to add an integer to a complex number , we need not explicitly define a special coercion procedure integer->complex . Instead, we define how an integer can be transformed into a rational number , how a rational number is transformed into a real number , and how a real number is transformed into a complex number . We then allow the system to transform the integer into a complex number through these steps and then add the two complex numbers. We can redesign our apply-generic  procedure in the following way: For each type, we need to supply a raise  procedure, which “raises” objects of that type one level in the tower . Then when the system is required to operate on objects of dif ferent types it can successively raise the lowe


key: chroma:document
string_value:
r types until all the objects are at the same level in the tower . (Exercises 2.83 and  2.84 concern the details of implementing such a strategy .) Another advantage of a tower is that we can easily implement the notion that every type “inherits” all operations defined on a supertype. For instance, if we do not supply a special procedure for finding the real part of an integer , we should nevertheless expect that real-part  will be defined for integers by virtue of the fact that integers are a subtype of complex numbers. In a tower , we can arrange for this to happen in a uniform way by modifying apply-generic . If the required operation is not directly definedfor the type of the object given, we raise the object to its supertype and try again. We thus crawl up the tower , transforming our ar gument as we go, until we either find a level at which the desired operation can be performed or hit the top (in which case we give up). Yet another advantage of a tower over a more general hierarchy is that it gives us 


key: chroma:document
string_value:
a simple way to “lower” a data object to the simplest representation. For example, if we add 2 + 3 i to 4 - 3 i, it would be nice to obtain the answer as the integer 6 rather than as the complex number 6 + 0 i. Exercise 2.85 discusses a way to implement such a lowering operation. (The trick is that we need a general way to distinguish those objects that can be lowered, such as 6 + 0 i, from those that cannot, such as 6 + 2 i.) Figur e 2.26:   Relations among types of geometric figures. Inadequacies of hierar chies If the data types in our system can be naturally arranged in a tower , this greatly simplifies the problems of dealing with generic operations on different types, as we have seen. Unfortunately , this is usually not the case. Figure 2.26 illustrates a more complex arrangement of mixed types, this oneshowing relations among dif ferent types of geometric figures. We see that, in general, a type may have more than one subtype. Triangles and quadrilaterals, for instance, are both subtypes of polygons. I


key: chroma:document
string_value:
n addition, a type may have more than one supertype. For example, an isosceles right triangle may be regarded either as an isosceles triangle or as a right triangle. This multiple-supertypes issue is particularly thorny , since it means that there is no unique way to “raise” a type in the hierarchy . Finding the “correct” supertype in which to apply an operation to an object may involve considerable searching through the entire type network on the part of a procedure such as apply-generic . Since there generally are multiple subtypes for a type, there is a similar problem in coercing a value “down” the type hierarchy . Dealing with lar ge numbers of interrelated types while still preserving modularity in the design of lar ge systems is very dif ficult, and is an area of much current research.52 Exer cise 2.81.   Louis Reasoner has noticed that apply-generic  may try to coerce the ar guments to each other's type even if they already have the same type. Therefore, he reasons, we need to put procedures in the co


key: chroma:document
string_value:
ercion table to "coerce" ar guments of each type to their own type. For example, in addition to the scheme-number->complex  coercion shown above, he would do: (define (scheme-number->scheme-number n) n)   (define (complex->complex z) z)   (put-coercion 'scheme-number 'scheme-number                 scheme-number->scheme-number)   (put-coercion 'complex 'complex complex->complex)   a. With Louis's coercion procedures installed, what happens if apply- generic  is called with two ar guments of type scheme-number  or two arguments of type complex  for an operation that is not found in the table for those types? For example, assume that we've defined a generic exponentiation operation: (define (exp x y) (apply-generic 'exp x y))   and have put a procedure for exponentiation in the Scheme-number package but not in any other package:;; following added to Scheme-number package   (put 'exp '(scheme-number scheme-number)        (lambda (x y) (tag (expt x y)))) ; using primitive expt  What happens if we call exp with two


key: chroma:document
string_value:
 complex numbers as ar guments? b. Is Louis correct that something had to be done about coercion with arguments of the same type, or does apply-generic  work correctly as is? c. Modify apply-generic  so that it doesn't try coercion if the two arguments have the same type. Exer cise 2.82.   Show how to generalize apply-generic  to handle coercion in the general case of multiple ar guments. One strategy is to attempt to coerce all the ar guments to the type of the first ar gument, then to the type of the second ar gument, and so on. Give an example of a situation where this strategy (and likewise the two-ar gument version given above) is not sufficiently general. (Hint: Consider the case where there are some suitable mixed-type operations present in the table that will not be tried.) Exer cise 2.83.   Suppose you are designing a generic arithmetic system for dealing with the tower of types shown in figure 2.25: integer , rational, real, complex. For each type (except complex), design a procedure that raises obj


key: chroma:document
string_value:
ects of that type one level in the tower . Show how to install a generic raise  operation that will work for each type (except complex). Exer cise 2.84.   Using the raise  operation of exercise 2.83, modify the apply-generic  procedure so that it coerces its ar guments to have the same type by the method of successive raising, as discussed in this section. You will need to devise a way to test which of two types is higher in the tower . Do this in a manner that is “compatible” with the rest of the system and will not lead to problems in adding new levels to the tower . Exer cise 2.85.   This section mentioned a method for “simplifying” a data object by lowering it in the tower of types as far as possible. Design a procedure drop  that accomplishes this for the tower described in exercise 2.83. The key is to decide, in some general way , whether an object can be lowered. For example, the complex number 1.5 + 0 i can be loweredas far as real , the complex number 1 + 0 i can be lowered as far as integer , and th


key: chroma:document
string_value:
e complex number 2 + 3 i cannot be lowered at all. Here is a plan for determining whether an object can be lowered: Begin by defining a generic operation project  that “pushes” an object down in the tower . For example, projecting a complex number would involve throwing away the imaginary part. Then a number can be dropped if, when we project  it and raise  the result back to the type we started with, we end up with something equal to what we started with. Show how to implement this idea in detail, by writing a drop  procedure that drops an object as far as possible. You will need to design the various projection operations53 and install project  as a generic operation in the system. You will also need to make use of a generic equality predicate, such as described in exercise 2.79. Finally , use drop  to rewrite apply-generic  from exercise 2.84 so that it “simplifies” its answers. Exer cise 2.86.   Suppose we want to handle complex numbers whose real parts, imaginary parts, magnitudes, and angles can be eith


key: chroma:document
string_value:
er ordinary numbers, rational numbers, or other numbers we might wish to add to the system. Describe and implement the changes to the system needed to accommodate this. You will have to define operations such as sine  and cosine  that are generic over ordinary numbers and rational numbers. 2.5.3  Example: Symbolic Algebra The manipulation of symbolic algebraic expressions is a complex process that illustrates many of the hardest problems that occur in the design of large-scale systems. An algebraic expression, in general, can be viewed as a hierarchical structure, a tree of operators applied to operands. We can construct algebraic expressions by starting with a set of primitive objects, such as constants and variables, and combining these by means of algebraic operators, such as addition and multiplication. As in other languages, we form abstractions that enable us to refer to compound objects in simple terms. Typical abstractions in symbolic algebra are ideas such as linear combination, polynomial, rational 


key: chroma:document
string_value:
function, or trigonometric function. We can regard these as compound “types,” which are often useful for directing the processing of expressions. For example, we could describe the expressionas a polynomial in x with coef ficients that are trigonometric functions of polynomials in y whose coef ficients are integers. We will not attempt to develop a complete algebraic-manipulation system here. Such systems are exceedingly complex programs, embodying deep algebraic knowledge and elegant algorithms. What we will do is look at a simple but important part of algebraic manipulation: the arithmetic of polynomials. We will illustrate the kinds of decisions the designer of such a system faces, and how to apply the ideas of abstract data and generic operations to help or ganize this ef fort. Arithmetic on polynomials Our first task in designing a system for performing arithmetic on polynomials is to decide just what a polynomial is. Polynomials are normally defined relative to certain variables (the indeterminates  of 


key: chroma:document
string_value:
the polynomial). For simplicity , we will restrict ourselves to polynomials having just one indeterminate ( univariate polynomials ).54 We will define a polynomial to be a sum of terms, each of which is either a coef ficient, a power of the indeterminate, or a product of a coef ficient and a power of the indeterminate. A coef ficient is defined as an algebraic expression that is not dependent upon the indeterminate of the polynomial. For example, is a simple polynomial in x, and is a polynomial in x whose coef ficients are polynomials in y. Already we are skirting some thorny issues. Is the first of these polynomials the same as the polynomial 5 y2 + 3y + 7, or not? A reasonable answer might be “yes, if we are considering a polynomial purely as a mathematical function, but no, if we are considering a polynomial to be a syntactic form.” The second polynomial is algebraically equivalent to a polynomial in ywhose coef ficients are polynomials in x. Should our system recognize this, or not? Furthermore, there are


key: chroma:document
string_value:
 other ways to represent a polynomial – for example, as a product of factors, or (for a univariate polynomial) as the set of roots, or as a listing of the values of the polynomial at a specified set of points.55 We can finesse these questions by deciding that in our algebraic- manipulation system a “polynomial” will be a particular syntactic form, not its underlying mathematical meaning. Now we must consider how to go about doing arithmetic on polynomials. In this simple system, we will consider only addition and multiplication. Moreover , we will insist that two polynomials to be combined must have the same indeterminate. We will approach the design of our system by following the familiar discipline of data abstraction. We will represent polynomials using a data structure called a poly, which consists of a variable and a collection of terms. We assume that we have selectors variable  and term-list  that extract those parts from a poly and a constructor make-poly  that assembles a poly from a given variable a


key: chroma:document
string_value:
nd a term list. A variable will be just a symbol, so we can use the same-variable?  procedure of section 2.3.2  to compare variables. The following procedures define addition and multiplication of polys: (define (add-poly p1 p2)     (if (same-variable? (variable p1) (variable p2))         (make-poly (variable p1)                    (add-terms (term-list p1)                               (term-list p2)))         (error "Polys not in same var -- ADD-POLY"                (list p1 p2))))   (define (mul-poly p1 p2)     (if (same-variable? (variable p1) (variable p2))         (make-poly (variable p1)                    (mul-terms (term-list p1)                               (term-list p2)))         (error "Polys not in same var -- MUL-POLY"                (list p1 p2))))  To incorporate polynomials into our generic arithmetic system, we need to supply them with type tags. We'll use the tag polynomial , and install appropriate operations on tagged polynomials in the operation table. We'll embed all our code in an in


key: chroma:document
string_value:
stallation procedure for the polynomial package, similar to the ones in section 2.5.1 : (define (install-polynomial-package)     ;; internal procedures     ;; representation of poly     (define (make-poly variable term-list)       (cons variable term-list))     (define (variable p) (car p))     (define (term-list p) (cdr p))     <procedures same-variable?  and variable?  from section 2.3.2>    ;; representation of terms and term lists     <procedures adjoin-term ...coeff from text below >      ;; continued on next page       (define (add-poly p1 p2) ...)    <procedures used by add-poly >    (define (mul-poly p1 p2) ...)    <procedures used by mul-poly >    ;; interface to rest of the system     (define (tag p) (attach-tag 'polynomial p))     (put 'add '(polynomial polynomial)          (lambda (p1 p2) (tag (add-poly p1 p2))))     (put 'mul '(polynomial polynomial)          (lambda (p1 p2) (tag (mul-poly p1 p2))))     (put 'make 'polynomial          (lambda (var terms) (tag (make-poly var terms))))     'done)  


key: chroma:document
string_value:
 Polynomial addition is performed termwise. Terms of the same order (i.e., with the same power of the indeterminate) must be combined. This is done by forming a new term of the same order whose coef ficient is the sum of the coef ficients of the addends. Terms in one addend for which there are noterms of the same order in the other addend are simply accumulated into the sum polynomial being constructed. In order to manipulate term lists, we will assume that we have a constructor the-empty-termlist  that returns an empty term list and a constructor adjoin-term  that adjoins a new term to a term list. We will also assume that we have a predicate empty-termlist?  that tells if a given term list is empty , a selector first-term  that extracts the highest-order term from a term list, and a selector rest-terms  that returns all but the highest-order term. To manipulate terms, we will suppose that we have a constructor make-term  that constructs a term with given order and coef ficient, and selectors order  and coef


key: chroma:document
string_value:
f  that return, respectively , the order and the coefficient of the term. These operations allow us to consider both terms and term lists as data abstractions, whose concrete representations we can worry about separately . Here is the procedure that constructs the term list for the sum of two polynomials:56 (define (add-terms L1 L2)     (cond ((empty-termlist? L1) L2)           ((empty-termlist? L2) L1)           (else            (let ((t1 (first-term L1)) (t2 (first-term L2)))              (cond ((> (order t1) (order t2))                     (adjoin-term                      t1 (add-terms (rest-terms L1) L2)))                    ((< (order t1) (order t2))                     (adjoin-term                      t2 (add-terms L1 (rest-terms L2))))                    (else                     (adjoin-term                      (make-term (order t1)                                 (add (coeff t1) (coeff t2)))                      (add-terms (rest-terms L1)                                 (rest-terms L2)))))))))  Th


key: chroma:document
string_value:
e most important point to note here is that we used the generic addition procedure add to add together the coef ficients of the terms being combined. This has powerful consequences, as we will see below . In order to multiply two term lists, we multiply each term of the first list by all the terms of the other list, repeatedly using mul-term-by-all-terms , which multiplies a given term by all terms in a given term list. The resulting term lists (one for each term of the first list) are accumulated into a sum. Multiplying two terms forms a term whose order is the sum of the orders of the factors and whose coef ficient is the product of the coef ficients of the factors: (define (mul-terms L1 L2)     (if (empty-termlist? L1)         (the-empty-termlist)         (add-terms (mul-term-by-all-terms (first-term L1) L2)                    (mul-terms (rest-terms L1) L2))))   (define (mul-term-by-all-terms t1 L)     (if (empty-termlist? L)         (the-empty-termlist)         (let ((t2 (first-term L)))           (adjoin


key: chroma:document
string_value:
-term            (make-term (+ (order t1) (order t2))                       (mul (coeff t1) (coeff t2)))            (mul-term-by-all-terms t1 (rest-terms L))))))   This is really all there is to polynomial addition and multiplication. Notice that, since we operate on terms using the generic procedures add and mul, our polynomial package is automatically able to handle any type of coefficient that is known about by the generic arithmetic package. If we include a coercion mechanism such as one of those discussed in section 2.5.2 , then we also are automatically able to handle operations on polynomials of dif ferent coef ficient types, such as Because we installed the polynomial addition and multiplication procedures add-poly  and mul-poly  in the generic arithmetic system as the add and mul operations for type polynomial , our system is also automatically able to handle polynomial operations such as The reason is that when the system tries to combine coef ficients, it will dispatch through add and mul. Since th


key: chroma:document
string_value:
e coef ficients are themselves polynomials (in y), these will be combined using add-poly  and mul-poly . The result is a kind of “data-directed recursion” in which, for example, a call to mul-poly  will result in recursive calls to mul-poly  in order to multiply the coef ficients. If the coef ficients of the coef ficients were themselves polynomials (as might be used to represent polynomials in three variables), the data direction would ensure that the system would follow through another level of recursive calls, and so on through as many levels as the structure of the data dictates.57 Repr esenting term lists Finally , we must confront the job of implementing a good representation for term lists. A term list is, in ef fect, a set of coef ficients keyed by the order of the term. Hence, any of the methods for representing sets, as discussed in section 2.3.3 , can be applied to this task. On the other hand, our procedures add-terms  and mul-terms  always access term lists sequentially from highest to lowest ord


key: chroma:document
string_value:
er . Thus, we will use some kind of ordered list representation. How should we structure the list that represents a term list? One consideration is the “density” of the polynomials we intend to manipulate. A polynomial is said to be dense  if it has nonzero coef ficients in terms of most orders. If it has many zero terms it is said to be sparse . For example, is a dense polynomial, whereas is sparse. The term lists of dense polynomials are most ef ficiently represented as lists of the coef ficients. For example, A above would be nicely represented as (1 2 0 3 -2 -5) . The order of a term in this representation is the length of the sublist beginning with that term's coef ficient, decremented by 1.58 This would be a terrible representation for a sparse polynomial such as B: There would be a giant list of zeros punctuated by a few lonely nonzero terms. A more reasonable representation of the term list of a sparse polynomial is as a list of the nonzero terms, where each term is a list containing the order of the 


key: chroma:document
string_value:
term and the coef ficient for that order . In such a scheme, polynomial B is efficiently represented as ((100 1) (2 2) (0 1)) . As most polynomial manipulations are performed on sparse polynomials, we will use this method. We will assume that term lists are represented as lists of terms, arranged from highest-order to lowest-order term. Once we have made this decision, implementing the selectors and constructors for terms and term lists is straightforward:59 (define (adjoin-term term term-list)     (if (=zero? (coeff term))         term-list         (cons term term-list)))   (define (the-empty-termlist) '())   (define (first-term term-list) (car term-list))   (define (rest-terms term-list) (cdr term-list))   (define (empty-termlist? term-list) (null? term-list))   (define (make-term order coeff) (list order coeff))   (define (order term) (car term))   (define (coeff term) (cadr term))   where =zero?  is as defined in exercise 2.80. (See also exercise 2.87 below .) Users of the polynomial package will create (


key: chroma:document
string_value:
tagged) polynomials by means of the procedure: (define (make-polynomial var terms)     ((get 'make 'polynomial) var terms))  Exer cise 2.87.   Install =zero?  for polynomials in the generic arithmetic package. This will allow adjoin-term  to work for polynomials with coefficients that are themselves polynomials. Exer cise 2.88.   Extend the polynomial system to include subtraction of polynomials. (Hint: Y ou may find it helpful to define a generic negation operation.) Exer cise 2.89.   Define procedures that implement the term-list representation described above as appropriate for dense polynomials. Exer cise 2.90.   Suppose we want to have a polynomial system that is efficient for both sparse and dense polynomials. One way to do this is to allow both kinds of term-list representations in our system. The situation is analogous to the complex-number example of section 2.4, where we allowed both rectangular and polar representations. To do this we must distinguish dif ferent types of term lists and make the ope


key: chroma:document
string_value:
rations on term lists generic. Redesign the polynomial system to implement this generalization. This is a major ef fort, not a local change. Exer cise 2.91.   A univariate polynomial can be divided by another one to produce a polynomial quotient and a polynomial remainder . For example, Division can be performed via long division. That is, divide the highest- order term of the dividend by the highest-order term of the divisor . The result is the first term of the quotient. Next, multiply the result by the divisor , subtract that from the dividend, and produce the rest of the answer by recursively dividing the dif ference by the divisor . Stop when the order of the divisor exceeds the order of the dividend and declare the dividend to be the remainder . Also, if the dividend ever becomes zero, return zero as both quotient and remainder . We can design a div-poly  procedure on the model of add-poly  and mul- poly . The procedure checks to see if the two polys have the same variable. If so, div-poly  strips of f 


key: chroma:document
string_value:
the variable and passes the problem to div-terms ,which performs the division operation on term lists. Div-poly  finally reattaches the variable to the result supplied by div-terms . It is convenient to design div-terms  to compute both the quotient and the remainder of a division. Div-terms  can take two term lists as ar guments and return a list of the quotient term list and the remainder term list. Complete the following definition of div-terms  by filling in the missing expressions. Use this to implement div-poly , which takes two polys as arguments and returns a list of the quotient and remainder polys. (define (div-terms L1 L2)     (if (empty-termlist? L1)         (list (the-empty-termlist) (the-empty-termlist))         (let ((t1 (first-term L1))               (t2 (first-term L2)))           (if (> (order t2) (order t1))               (list (the-empty-termlist) L1)               (let ((new-c (div (coeff t1) (coeff t2)))                     (new-o (- (order t1) (order t2))))                 (let ((rest-o


key: chroma:document
string_value:
f-result                        < compute rest of result recursively >                       ))                   < form complete result >                  ))))))   Hierar chies of types in symbolic algebra Our polynomial system illustrates how objects of one type (polynomials) may in fact be complex objects that have objects of many dif ferent types as parts. This poses no real dif ficulty in defining generic operations. We need only install appropriate generic operations for performing the necessary manipulations of the parts of the compound types. In fact, we saw that polynomials form a kind of “recursive data abstraction,” in that parts of a polynomial may themselves be polynomials. Our generic operations and our data-directed programming style can handle this complication without much trouble.On the other hand, polynomial algebra is a system for which the data types cannot be naturally arranged in a tower . For instance, it is possible to have polynomials in x whose coef ficients are polynomials in y. It


key: chroma:document
string_value:
 is also possible to have polynomials in y whose coef ficients are polynomials in x. Neither of these types is “above” the other in any natural way , yet it is often necessary to add together elements from each set. There are several ways to do this. One possibility is to convert one polynomial to the type of the other by expanding and rearranging terms so that both polynomials have the same principal variable. One can impose a towerlike structure on this by ordering the variables and thus always converting any polynomial to a “canonical form” with the highest-priority variable dominant and the lower -priority variables buried in the coef ficients. This strategy works fairly well, except that the conversion may expand a polynomial unnecessarily , making it hard to read and perhaps less ef ficient to work with. The tower strategy is certainly not natural for this domain or for any domain where the user can invent new types dynamically using old types in various combining forms, such as trigonometric functions,


key: chroma:document
string_value:
 power series, and integrals. It should not be surprising that controlling coercion is a serious problem in the design of lar ge-scale algebraic-manipulation systems. Much of the complexity of such systems is concerned with relationships among diverse types. Indeed, it is fair to say that we do not yet completely understand coercion. In fact, we do not yet completely understand the concept of a data type. Nevertheless, what we know provides us with powerful structuring and modularity principles to support the design of lar ge systems. Exer cise 2.92.   By imposing an ordering on variables, extend the polynomial package so that addition and multiplication of polynomials works for polynomials in dif ferent variables. (This is not easy!) Extended exer cise: Rational functions We can extend our generic arithmetic system to include rational functions . These are “fractions” whose numerator and denominator are polynomials, such as The system should be able to add, subtract, multiply , and divide rational functions,


key: chroma:document
string_value:
 and to perform such computations as (Here the sum has been simplified by removing common factors. Ordinary “cross multiplication” would have produced a fourth-degree polynomial over a fifth-degree polynomial.) If we modify our rational-arithmetic package so that it uses generic operations, then it will do what we want, except for the problem of reducing fractions to lowest terms. Exer cise 2.93.   Modify the rational-arithmetic package to use generic operations, but change make-rat  so that it does not attempt to reduce fractions to lowest terms. Test your system by calling make-rational  on two polynomials to produce a rational function (define p1 (make-polynomial 'x '((2 1)(0 1))))   (define p2 (make-polynomial 'x '((3 1)(0 1))))   (define rf (make-rational p2 p1))   Now add rf to itself, using add. You will observe that this addition procedure does not reduce fractions to lowest terms. We can reduce polynomial fractions to lowest terms using the same idea we used with integers: modifying make-rat  to divi


key: chroma:document
string_value:
de both the numerator and the denominator by their greatest common divisor . The notion of “greatest common divisor” makes sense for polynomials. In fact, we can compute the GCD of two polynomials using essentially the same Euclid's Algorithm that works for integers.60 The integer version is (define (gcd a b)     (if (= b 0)         a        (gcd b (remainder a b))))  Using this, we could make the obvious modification to define a GCD operation that works on term lists: (define (gcd-terms a b)     (if (empty-termlist? b)         a        (gcd-terms b (remainder-terms a b))))   where remainder-terms  picks out the remainder component of the list returned by the term-list division operation div-terms  that was implemented in exercise 2.91. Exer cise 2.94.   Using div-terms , implement the procedure remainder- terms  and use this to define gcd-terms  as above. Now write a procedure gcd-poly  that computes the polynomial GCD of two polys. (The procedure should signal an error if the two polys are not in the same v


key: chroma:document
string_value:
ariable.) Install in the system a generic operation greatest-common-divisor  that reduces to gcd-poly  for polynomials and to ordinary gcd for ordinary numbers. As a test, try (define p1 (make-polynomial 'x '((4 1) (3 -1) (2 -2) (1 2))))   (define p2 (make-polynomial 'x '((3 1) (1 -1))))   (greatest-common-divisor p1 p2)   and check your result by hand. Exer cise 2.95.   Define P1, P2, and P3 to be the polynomials Now define Q1 to be the product of P1 and P2 and Q2 to be the product of P1 and P3, and use greatest-common-divisor  (exercise 2.94) to compute the GCD of Q1 and Q2. Note that the answer is not the same as P1. This example introduces noninteger operations into the computation, causingdifficulties with the GCD algorithm.61 To understand what is happening, try tracing gcd-terms  while computing the GCD or try performing the division by hand. We can solve the problem exhibited in exercise 2.95 if we use the following modification of the GCD algorithm (which really works only in the case of polynomials 


key: chroma:document
string_value:
with integer coef ficients). Before performing any polynomial division in the GCD computation, we multiply the dividend by an integer constant factor , chosen to guarantee that no fractions will arise during the division process. Our answer will thus dif fer from the actual GCD by an integer constant factor , but this does not matter in the case of reducing rational functions to lowest terms; the GCD will be used to divide both the numerator and denominator , so the integer constant factor will cancel out. More precisely , if P and Q are polynomials, let O1 be the order of P (i.e., the order of the lar gest term of P) and let O2 be the order of Q. Let c be the leading coef ficient of Q. Then it can be shown that, if we multiply P by the integerizing factor  c1+O1 -O2, the resulting polynomial can be divided by Q by using the div-terms  algorithm without introducing any fractions. The operation of multiplying the dividend by this constant and then dividing is sometimes called the pseudodivision  of P by Q. The


key: chroma:document
string_value:
 remainder of the division is called the pseudor emainder . Exer cise 2.96.   a.    Implement the procedure pseudoremainder-terms , which is just like remainder-terms  except that it multiplies the dividend by the integerizing factor described above before calling div-terms . Modify gcd-terms  to use pseudoremainder-terms , and verify that greatest- common-divisor  now produces an answer with integer coef ficients on the example in exercise 2.95. b.    The GCD now has integer coef ficients, but they are lar ger than those of P1. Modify gcd-terms  so that it removes common factors from the coefficients of the answer by dividing all the coef ficients by their (integer) greatest common divisor . Thus, here is how to reduce a rational function to lowest terms:Compute the GCD of the numerator and denominator , using the version of gcd-terms  from exercise 2.96. When you obtain the GCD, multiply both numerator and denominator by the same integerizing factor before dividing through by the GCD, so that division by th


key: chroma:document
string_value:
e GCD will not introduce any noninteger coefficients. As the factor you can use the leading coef ficient of the GCD raised to the power 1 + O1 - O2, where O2 is the order of the GCD and O1 is the maximum of the orders of the numerator and denominator . This will ensure that dividing the numerator and denominator by the GCD will not introduce any fractions. The result of this operation will be a numerator and denominator with integer coef ficients. The coef ficients will normally be very lar ge because of all of the integerizing factors, so the last step is to remove the redundant factors by computing the (integer) greatest common divisor of all the coef ficients of the numerator and the denominator and dividing through by this factor . Exer cise 2.97.   a. Implement this algorithm as a procedure reduce-terms that takes two term lists n and d as ar guments and returns a list nn, dd, which are n and d reduced to lowest terms via the algorithm given above. Also write a procedure reduce-poly , analogous to add-po


key: chroma:document
string_value:
ly , that checks to see if the two polys have the same variable. If so, reduce-poly  strips of f the variable and passes the problem to reduce-terms , then reattaches the variable to the two term lists supplied by reduce-terms . b. Define a procedure analogous to reduce-terms  that does what the original make-rat  did for integers: (define (reduce-integers n d)     (let ((g (gcd n d)))       (list (/ n g) (/ d g))))   and define reduce  as a generic operation that calls apply-generic  to dispatch to either reduce-poly  (for polynomial  arguments) or reduce- integers  (for scheme-number  arguments). You can now easily make the rational-arithmetic package reduce fractions to lowest terms by havingmake-rat  call reduce  before combining the given numerator and denominator to form a rational number . The system now handles rational expressions in either integers or polynomials. To test your program, try the example at the beginning of this extended exercise: (define p1 (make-polynomial 'x '((1 1)(0 1))))   (defin


key: chroma:document
string_value:
e p2 (make-polynomial 'x '((3 1)(0 -1))))   (define p3 (make-polynomial 'x '((1 1))))   (define p4 (make-polynomial 'x '((2 1)(0 -1))))     (define rf1 (make-rational p1 p2))   (define rf2 (make-rational p3 p4))     (add rf1 rf2)   See if you get the correct answer , correctly reduced to lowest terms. The GCD computation is at the heart of any system that does operations on rational functions. The algorithm used above, although mathematically straightforward, is extremely slow . The slowness is due partly to the lar ge number of division operations and partly to the enormous size of the intermediate coef ficients generated by the pseudodivisions. One of the active areas in the development of algebraic-manipulation systems is the design of better algorithms for computing polynomial GCDs.62 49 We also have to supply an almost identical procedure to handle the types (scheme-number complex) . 50 See exercise 2.82 for generalizations. 51 If we are clever , we can usually get by with fewer than n2 coercion procedur


key: chroma:document
string_value:
es. For instance, if we know how to convert from type 1 to type 2 and from type 2 to type 3, then we can use this knowledge to convert from type 1 to type 3. This can greatly decrease the number of coercion procedures we need to supply explicitly when we add a new type to the system. If we are willing to build the required amount of sophistication into our system, we can have it search the “graph” of relations among types and automatically generate those coercion procedures that can be inferred from the ones that are supplied explicitly . 52 This statement, which also appears in the first edition of this book, is just as true now as it was when we wrote it twelve years ago. Developing a useful, general framework for expressing therelations among dif ferent types of entities (what philosophers call “ontology”) seems intractably difficult. The main dif ference between the confusion that existed ten years ago and the confusion that exists now is that now a variety of inadequate ontological theories have been emb


key: chroma:document
string_value:
odied in a plethora of correspondingly inadequate programming languages. For example, much of the complexity of object-oriented programming languages – and the subtle and confusing dif ferences among contemporary object-oriented languages – centers on the treatment of generic operations on interrelated types. Our own discussion of computational objects in chapter 3 avoids these issues entirely . Readers familiar with object-oriented programming will notice that we have much to say in chapter 3 about local state, but we do not even mention “classes” or “inheritance.” In fact, we suspect that these problems cannot be adequately addressed in terms of computer -language design alone, without also drawing on work in knowledge representation and automated reasoning. 53 A real number can be projected to an integer using the round  primitive, which returns the closest integer to its ar gument. 54 On the other hand, we will allow polynomials whose coef ficients are themselves polynomials in other variables. This will 


key: chroma:document
string_value:
give us essentially the same representational power as a full multivariate system, although it does lead to coercion problems, as discussed below . 55 For univariate polynomials, giving the value of a polynomial at a given set of points can be a particularly good representation. This makes polynomial arithmetic extremely simple. To obtain, for example, the sum of two polynomials represented in this way , we need only add the values of the polynomials at corresponding points. To transform back to a more familiar representation, we can use the Lagrange interpolation formula, which shows how to recover the coef ficients of a polynomial of degree n given the values of the polynomial at n + 1 points. 56 This operation is very much like the ordered union-set  operation we developed in exercise  2.62. In fact, if we think of the terms of the polynomial as a set ordered according to the power of the indeterminate, then the program that produces the term list for a sum is almost identical to union- set. 57 To make thi


key: chroma:document
string_value:
s work completely smoothly , we should also add to our generic arithmetic system the ability to coerce a “number” to a polynomial by regarding it as a polynomial of degree zero whose coefficient is the number . This is necessary if we are going to perform operations such as which requires adding the coef ficient y + 1 to the coef ficient 2. 58 In these polynomial examples, we assume that we have implemented the generic arithmetic system using the type mechanism suggested in exercise 2.78. Thus, coef ficients that are ordinary numbers will be represented as the numbers themselves rather than as pairs whose car is the symbol scheme- number . 59 Although we are assuming that term lists are ordered, we have implemented adjoin-term  to simply cons  the new term onto the existing term list. We can get away with this so long as we guarantee that the procedures (such as add-terms ) that use adjoin-term  always call it with a higher - order term than appears in the list. If we did not want to make such a guarantee, we


key: chroma:document
string_value:
 could have implemented adjoin-term  to be similar to the adjoin-set  constructor for the ordered-list representation of sets (exercise 2.61).60 The fact that Euclid's Algorithm works for polynomials is formalized in algebra by saying that polynomials form a kind of algebraic domain called a Euclidean ring . A Euclidean ring is a domain that admits addition, subtraction, and commutative multiplication, together with a way of assigning to each element x of the ring a positive integer “measure” m(x) with the properties that m(xy)> m(x) for any nonzero x and y and that, given any x and y, there exists a q such that y = qx + r and either r = 0 or m(r)< m(x). From an abstract point of view , this is what is needed to prove that Euclid's Algorithm works. For the domain of integers, the measure m of an integer is the absolute value of the integer itself. For the domain of polynomials, the measure of a polynomial is its degree. 61 In an implementation like MIT Scheme, this produces a polynomial that is indeed a divis


key: chroma:document
string_value:
or of Q1 and Q2, but with rational coef ficients. In many other Scheme systems, in which division of integers can produce limited-precision decimal numbers, we may fail to get a valid divisor . 62 One extremely ef ficient and elegant method for computing polynomial GCDs was discovered by Richard Zippel (1979). The method is a probabilistic algorithm, as is the fast test for primality that we discussed in chapter 1. Zippel's book (1993) describes this method, together with other ways to compute polynomial GCDs.Chapter 3 Modularity , Objects, and State M (Even while it changes, it stands still.) Heraclitus Plus ça change, plus c'est la même chose. Alphonse Karr The preceding chapters introduced the basic elements from which programs are made. We saw how primitive procedures and primitive data are combined to construct compound entities, and we learned that abstraction is vital in helping us to cope with the complexity of lar ge systems. But these tools are not suf ficient for designing programs. Effective progr


key: chroma:document
string_value:
am synthesis also requires or ganizational principles that can guide us in formulating the overall design of a program. In particular , we need strategies to help us structure lar ge systems so that they will be modular , that is, so that they can be divided “naturally” into coherent parts that can be separately developed and maintained. One powerful design strategy , which is particularly appropriate to the construction of programs for modeling physical systems, is to base the structure of our programs on the structure of the system being modeled. For each object in the system, we construct a corresponding computational object. For each system action, we define a symbolic operation in our computational model. Our hope in using this strategy is that extending the model to accommodate new objects or new actions will require no strategic changes to the program, only the addition of the new symbolic analogs of those objects or actions. If we have been successful in our system organization, then to add a new feat


key: chroma:document
string_value:
ure or debug an old one we will have to work on only a localized part of the system.To a lar ge extent, then, the way we or ganize a lar ge program is dictated by our perception of the system to be modeled. In this chapter we will investigate two prominent or ganizational strategies arising from two rather different “world views” of the structure of systems. The first or ganizational strategy concentrates on objects , viewing a lar ge system as a collection of distinct objects whose behaviors may change over time. An alternative organizational strategy concentrates on the streams  of information that flow in the system, much as an electrical engineer views a signal-processing system. Both the object-based approach and the stream-processing approach raise significant linguistic issues in programming. With objects, we must be concerned with how a computational object can change and yet maintain its identity . This will force us to abandon our old substitution model of computation (section 1.1.5 ) in favor of a 


key: chroma:document
string_value:
more mechanistic but less theoretically tractable envir onment model  of computation. The dif ficulties of dealing with objects, change, and identity are a fundamental consequence of the need to grapple with time in our computational models. These difficulties become even greater when we allow the possibility of concurrent execution of programs. The stream approach can be most fully exploited when we decouple simulated time in our model from the order of the events that take place in the computer during evaluation. We will accomplish this using a technique known as delayed evaluation .3.1  Assignment and Local State We ordinarily view the world as populated by independent objects, each of which has a state that changes over time. An object is said to “have state” if its behavior is influenced by its history . A bank account, for example, has state in that the answer to the question “Can I withdraw $100?” depends upon the history of deposit and withdrawal transactions. We can characterize an object's state by 


key: chroma:document
string_value:
one or more state variables , which among them maintain enough information about history to determine the object's current behavior . In a simple banking system, we could characterize the state of an account by a current balance rather than by remembering the entire history of account transactions. In a system composed of many objects, the objects are rarely completely independent. Each may influence the states of others through interactions, which serve to couple the state variables of one object to those of other objects. Indeed, the view that a system is composed of separate objects is most useful when the state variables of the system can be grouped into closely coupled subsystems that are only loosely coupled to other subsystems. This view of a system can be a powerful framework for or ganizing computational models of the system. For such a model to be modular , it should be decomposed into computational objects that model the actual objects in the system. Each computational object must have its own loca


key: chroma:document
string_value:
l state variables  describing the actual object's state. Since the states of objects in the system being modeled change over time, the state variables of the corresponding computational objects must also change. If we choose to model the flow of time in the system by the elapsed time in the computer , then we must have a way to construct computational objects whose behaviors change as our programs run. In particular , if we wish to model state variables by ordinary symbolic names in the programming language, then the language must provide an assignment operator  to enable us to change the value associated with a name. 3.1.1  Local State V ariablesTo illustrate what we mean by having a computational object with time- varying state, let us model the situation of withdrawing money from a bank account. We will do this using a procedure withdraw , which takes as argument an amount  to be withdrawn. If there is enough money in the account to accommodate the withdrawal, then withdraw  should return the balance remai


key: chroma:document
string_value:
ning after the withdrawal. Otherwise, withdraw  should return the message Insufficient funds.  For example, if we begin with $100 in the account, we should obtain the following sequence of responses using withdraw : (withdraw 25)   75  (withdraw 25)   50  (withdraw 60)   "Insufficient funds"   (withdraw 15)   35  Observe that the expression (withdraw 25) , evaluated twice, yields different values. This is a new kind of behavior for a procedure. Until now , all our procedures could be viewed as specifications for computing mathematical functions. A call to a procedure computed the value of the function applied to the given ar guments, and two calls to the same procedure with the same ar guments always produced the same result.1 To implement withdraw , we can use a variable balance  to indicate the balance of money in the account and define withdraw  as a procedure that accesses balance . The withdraw  procedure checks to see if balance  is at least as lar ge as the requested amount . If so, withdraw  decrement


key: chroma:document
string_value:
s balance by amount  and returns the new value of balance . Otherwise, withdraw returns the Insufficient funds  message. Here are the definitions of balance and withdraw : (define balance 100)     (define (withdraw amount)     (if (>= balance amount)        (begin (set! balance (- balance amount))                balance)         "Insufficient funds"))   Decrementing balance  is accomplished by the expression (set! balance (- balance amount))   This uses the set!  special form, whose syntax is (set! <name> <new-value >)  Here < name > is a symbol and < new-value > is any expression. Set!  changes <name > so that its value is the result obtained by evaluating < new-value >. In the case at hand, we are changing balance  so that its new value will be the result of subtracting amount  from the previous value of balance .2 Withdraw  also uses the begin  special form to cause two expressions to be evaluated in the case where the if test is true: first decrementing balance and then returning the value of balance . In


key: chroma:document
string_value:
 general, evaluating the expression (begin < exp1> <exp2> ... <expk>)  causes the expressions < exp1> through < expk> to be evaluated in sequence and the value of the final expression < expk> to be returned as the value of the entire begin  form.3 Although withdraw  works as desired, the variable balance  presents a problem. As specified above, balance  is a name defined in the global environment and is freely accessible to be examined or modified by any procedure. It would be much better if we could somehow make balance internal to withdraw , so that withdraw  would be the only procedure that could access balance  directly and any other procedure could access balance  only indirectly (through calls to withdraw ). This would more accurately model the notion that balance  is a local state variable used by withdraw  to keep track of the state of the account.We can make balance  internal to withdraw  by rewriting the definition as follows: (define new-withdraw     (let ((balance 100))       (lambda (amount)     


key: chroma:document
string_value:
    (if (>= balance amount)             (begin (set! balance (- balance amount))                    balance)             "Insufficient funds"))))   What we have done here is use let to establish an environment with a local variable balance , bound to the initial value 100. Within this local environment, we use lambda  to create a procedure that takes amount  as an argument and behaves like our previous withdraw  procedure. This procedure – returned as the result of evaluating the let expression – is new- withdraw , which behaves in precisely the same way as withdraw  but whose variable balance  is not accessible by any other procedure.4 Combining set!  with local variables is the general programming technique we will use for constructing computational objects with local state. Unfortunately , using this technique raises a serious problem: When we first introduced procedures, we also introduced the substitution model of evaluation (section 1.1.5 ) to provide an interpretation of what procedure application mean


key: chroma:document
string_value:
s. We said that applying a procedure should be interpreted as evaluating the body of the procedure with the formal parameters replaced by their values. The trouble is that, as soon as we introduce assignment into our language, substitution is no longer an adequate model of procedure application. (We will see why this is so in section 3.1.3 .) As a consequence, we technically have at this point no way to understand why the new- withdraw  procedure behaves as claimed above. In order to really understand a procedure such as new-withdraw , we will need to develop a new model of procedure application. In section 3.2 we will introduce such a model, together with an explanation of set!  and local variables. First, however , we examine some variations on the theme established by new- withdraw .The following procedure, make-withdraw , creates “withdrawal processors.” The formal parameter balance  in make-withdraw  specifies the initial amount of money in the account.5 (define (make-withdraw balance)     (lambda (amoun


key: chroma:document
string_value:
t)       (if (>= balance amount)           (begin (set! balance (- balance amount))                  balance)           "Insufficient funds")))   Make-withdraw  can be used as follows to create two objects W1 and W2: (define W1 (make-withdraw 100))   (define W2 (make-withdraw 100))   (W1 50)  50  (W2 70)  30  (W2 40)  "Insufficient funds"   (W1 40)  10  Observe that W1 and W2 are completely independent objects, each with its own local state variable balance . Withdrawals from one do not af fect the other . We can also create objects that handle deposits as well as withdrawals, and thus we can represent simple bank accounts. Here is a procedure that returns a “bank-account object” with a specified initial balance: (define (make-account balance)     (define (withdraw amount)       (if (>= balance amount)           (begin (set! balance (- balance amount))                  balance)           "Insufficient funds"))    (define (deposit amount)       (set! balance (+ balance amount))       balance)     (define (disp


key: chroma:document
string_value:
atch m)       (cond ((eq? m 'withdraw) withdraw)             ((eq? m 'deposit) deposit)             (else (error "Unknown request -- MAKE-ACCOUNT"                          m))))     dispatch)   Each call to make-account  sets up an environment with a local state variable balance . Within this environment, make-account  defines procedures deposit  and withdraw  that access balance  and an additional procedure dispatch  that takes a “message” as input and returns one of the two local procedures. The dispatch  procedure itself is returned as the value that represents the bank-account object. This is precisely the message- passing  style of programming that we saw in section 2.4.3 , although here we are using it in conjunction with the ability to modify local variables. Make-account  can be used as follows: (define acc (make-account 100))   ((acc 'withdraw) 50)   50  ((acc 'withdraw) 60)   "Insufficient funds"   ((acc 'deposit) 40)   90  ((acc 'withdraw) 60)   30  Each call to acc returns the locally defined depo


key: chroma:document
string_value:
sit  or withdraw  procedure, which is then applied to the specified amount . As was the case with make- withdraw , another call to make-account (define acc2 (make-account 100))  will produce a completely separate account object, which maintains its own local balance . Exer cise 3.1.   An accumulator  is a procedure that is called repeatedly with a single numeric ar gument and accumulates its ar guments into a sum. Each time it is called, it returns the currently accumulated sum. Write a procedure make-accumulator  that generates accumulators, each maintaining an independent sum. The input to make-accumulator  should specify the initial value of the sum; for example (define A (make-accumulator 5))   (A 10)  15  (A 10)  25  Exer cise 3.2.   In software-testing applications, it is useful to be able to count the number of times a given procedure is called during the course of a computation. Write a procedure make-monitored  that takes as input a procedure, f, that itself takes one input. The result returned by ma


key: chroma:document
string_value:
ke- monitored  is a third procedure, say mf, that keeps track of the number of times it has been called by maintaining an internal counter . If the input to mf is the special symbol how-many-calls? , then mf returns the value of the counter . If the input is the special symbol reset-count , then mf resets the counter to zero. For any other input, mf returns the result of calling f on that input and increments the counter . For instance, we could make a monitored version of the sqrt  procedure: (define s (make-monitored sqrt))     (s 100)  10    (s 'how-many-calls?)   1  Exer cise 3.3.   Modify the make-account  procedure so that it creates password-protected accounts. That is, make-account  should take a symbolas an additional ar gument, as in (define acc (make-account 100 'secret-password))   The resulting account object should process a request only if it is accompanied by the password with which the account was created, and should otherwise return a complaint: ((acc 'secret-password 'withdraw) 40)   60    


key: chroma:document
string_value:
((acc 'some-other-password 'deposit) 50)   "Incorrect password"   Exer cise 3.4.   Modify the make-account  procedure of exercise 3.3 by adding another local state variable so that, if an account is accessed more than seven consecutive times with an incorrect password, it invokes the procedure call-the-cops . 3.1.2  The Benefits of Intr oducing Assignment As we shall see, introducing assignment into our programming language leads us into a thicket of dif ficult conceptual issues. Nevertheless, viewing systems as collections of objects with local state is a powerful technique for maintaining a modular design. As a simple example, consider the design of a procedure rand  that, whenever it is called, returns an integer chosen at random. It is not at all clear what is meant by “chosen at random.” What we presumably want is for successive calls to rand  to produce a sequence of numbers that has statistical properties of uniform distribution. We will not discuss methods for generating suitable sequences here. Rathe


key: chroma:document
string_value:
r , let us assume that we have a procedure rand-update  that has the property that if we start with a given number x1 and form x2 = (rand-update x1)  x3 = (rand-update x2) then the sequence of values x1, x2, x3, ..., will have the desired statistical properties.6 We can implement rand  as a procedure with a local state variable x that is initialized to some fixed value random-init . Each call to rand  computes rand-update  of the current value of x, returns this as the random number , and also stores this as the new value of x. (define rand     (let ((x random-init))       (lambda ()         (set! x (rand-update x))         x)))   Of course, we could generate the same sequence of random numbers without using assignment by simply calling rand-update  directly . However , this would mean that any part of our program that used random numbers would have to explicitly remember the current value of x to be passed as an ar gument to rand-update . To realize what an annoyance this would be, consider using random numb


key: chroma:document
string_value:
ers to implement a technique called Monte Carlo simulation . The Monte Carlo method consists of choosing sample experiments at random from a lar ge set and then making deductions on the basis of the probabilities estimated from tabulating the results of those experiments. For example, we can approximate π using the fact that 6/π2 is the probability that two integers chosen at random will have no factors in common; that is, that their greatest common divisor will be 1.7 To obtain the approximation to π, we perform a lar ge number of experiments. In each experiment we choose two integers at random and perform a test to see if their GCD is 1. The fraction of times that the test is passed gives us our estimate of 6/π2, and from this we obtain our approximation to π. The heart of our program is a procedure monte-carlo , which takes as arguments the number of times to try an experiment, together with the experiment, represented as a no-ar gument procedure that will return either true or false each time it is run. M


key: chroma:document
string_value:
onte-carlo  runs the experiment for thedesignated number of trials and returns a number telling the fraction of the trials in which the experiment was found to be true. (define (estimate-pi trials)     (sqrt (/ 6 (monte-carlo trials cesaro-test))))   (define (cesaro-test)      (= (gcd (rand) (rand)) 1))   (define (monte-carlo trials experiment)     (define (iter trials-remaining trials-passed)       (cond ((= trials-remaining 0)              (/ trials-passed trials))             ((experiment)              (iter (- trials-remaining 1) (+ trials-passed 1)))             (else              (iter (- trials-remaining 1) trials-passed))))     (iter trials 0))   Now let us try the same computation using rand-update  directly rather than rand , the way we would be forced to proceed if we did not use assignment to model local state: (define (estimate-pi trials)     (sqrt (/ 6 (random-gcd-test trials random-init))))   (define (random-gcd-test trials initial-x)     (define (iter trials-remaining trials-passed x)       (l


key: chroma:document
string_value:
et ((x1 (rand-update x)))         (let ((x2 (rand-update x1)))           (cond ((= trials-remaining 0)                    (/ trials-passed trials))                 ((= (gcd x1 x2) 1)                  (iter (- trials-remaining 1)                        (+ trials-passed 1)                        x2))                 (else                  (iter (- trials-remaining 1)                        trials-passed                       x2))))))     (iter trials 0 initial-x))   While the program is still simple, it betrays some painful breaches of modularity . In our first version of the program, using rand , we can express the Monte Carlo method directly as a general monte-carlo  procedure that takes as an ar gument an arbitrary experiment  procedure. In our second version of the program, with no local state for the random-number generator , random-gcd-test  must explicitly manipulate the random numbers x1 and x2 and recycle x2 through the iterative loop as the new input to rand-update . This explicit handling of the rand


key: chroma:document
string_value:
om numbers intertwines the structure of accumulating test results with the fact that our particular experiment uses two random numbers, whereas other Monte Carlo experiments might use one random number or three. Even the top- level procedure estimate-pi  has to be concerned with supplying an initial random number . The fact that the random-number generator's insides are leaking out into other parts of the program makes it dif ficult for us to isolate the Monte Carlo idea so that it can be applied to other tasks. In the first version of the program, assignment encapsulates the state of the random- number generator within the rand  procedure, so that the details of random- number generation remain independent of the rest of the program. The general phenomenon illustrated by the Monte Carlo example is this: From the point of view of one part of a complex process, the other parts appear to change with time. They have hidden time-varying local state. If we wish to write computer programs whose structure reflects t


key: chroma:document
string_value:
his decomposition, we make computational objects (such as bank accounts and random-number generators) whose behavior changes with time. We model state with local state variables, and we model the changes of state with assignments to those variables. It is tempting to conclude this discussion by saying that, by introducing assignment and the technique of hiding state in local variables, we are able to structure systems in a more modular fashion than if all state had to be manipulated explicitly , by passing additional parameters. Unfortunately , as we shall see, the story is not so simple.Exer cise 3.5.   Monte Carlo integration  is a method of estimating definite integrals by means of Monte Carlo simulation. Consider computing the area of a region of space described by a predicate P(x, y) that is true for points (x, y) in the region and false for points not in the region. For example, the region contained within a circle of radius 3 centered at (5, 7) is described by the predicate that tests whether ( x - 5)2


key: chroma:document
string_value:
 + (y - 7)2< 32. To estimate the area of the region described by such a predicate, begin by choosing a rectangle that contains the region. For example, a rectangle with diagonally opposite corners at (2, 4) and (8, 10) contains the circle above. The desired integral is the area of that portion of the rectangle that lies in the region. We can estimate the integral by picking, at random, points ( x,y) that lie in the rectangle, and testing P(x, y) for each point to determine whether the point lies in the region. If we try this with many points, then the fraction of points that fall in the region should give an estimate of the proportion of the rectangle that lies in the region. Hence, multiplying this fraction by the area of the entire rectangle should produce an estimate of the integral. Implement Monte Carlo integration as a procedure estimate-integral  that takes as ar guments a predicate P, upper and lower bounds x1, x2, y1, and y2 for the rectangle, and the number of trials to perform in order to produce t


key: chroma:document
string_value:
he estimate. Your procedure should use the same monte-carlo  procedure that was used above to estimate π. Use your estimate-integral  to produce an estimate of π by measuring the area of a unit circle. You will find it useful to have a procedure that returns a number chosen at random from a given range. The following random-in-range  procedure implements this in terms of the random  procedure used in section 1.2.6 , which returns a nonnegative number less than its input.8 (define (random-in-range low high)     (let ((range (- high low)))       (+ low (random range))))   Exer cise 3.6.   It is useful to be able to reset a random-number generator to produce a sequence starting from a given value. Design a new rand procedure that is called with an ar gument that is either the symbol generate or the symbol reset  and behaves as follows: (rand 'generate)  produces anew random number; ((rand 'reset) < new-value >) resets the internal state variable to the designated < new-value >. Thus, by resetting the state, one 


key: chroma:document
string_value:
can generate repeatable sequences. These are very handy to have when testing and debugging programs that use random numbers. 3.1.3  The Costs of Intr oducing Assignment As we have seen, the set!  operation enables us to model objects that have local state. However , this advantage comes at a price. Our programming language can no longer be interpreted in terms of the substitution model of procedure application that we introduced in section 1.1.5 . Moreover , no simple model with “nice” mathematical properties can be an adequate framework for dealing with objects and assignment in programming languages. So long as we do not use assignments, two evaluations of the same procedure with the same ar guments will produce the same result, so that procedures can be viewed as computing mathematical functions. Programming without any use of assignments, as we did throughout the first two chapters of this book, is accordingly known as functional programming . To understand how assignment complicates matters, consider a s


key: chroma:document
string_value:
implified version of the make-withdraw  procedure of section 3.1.1  that does not bother to check for an insuf ficient amount: (define (make-simplified-withdraw balance)     (lambda (amount)       (set! balance (- balance amount))       balance))   (define W (make-simplified-withdraw 25))   (W 20)  5  (W 10)  - 5  Compare this procedure with the following make-decrementer  procedure, which does not use set! :(define (make-decrementer balance)     (lambda (amount)       (- balance amount)))   Make-decrementer  returns a procedure that subtracts its input from a designated amount balance , but there is no accumulated ef fect over successive calls, as with make-simplified-withdraw : (define D (make-decrementer 25))   (D 20)  5  (D 10)  15  We can use the substitution model to explain how make-decrementer works. For instance, let us analyze the evaluation of the expression ((make-decrementer 25) 20)   We first simplify the operator of the combination by substituting 25 for balance  in the body of make-decrementer


key: chroma:document
string_value:
 . This reduces the expression to ((lambda (amount) (- 25 amount)) 20)   Now we apply the operator by substituting 20 for amount  in the body of the lambda  expression: (- 25 20)   The final answer is 5. Observe, however , what happens if we attempt a similar substitution analysis with make-simplified-withdraw : ((make-simplified-withdraw 25) 20)   We first simplify the operator by substituting 25 for balance  in the body of make-simplified-withdraw . This reduces the expression to9((lambda (amount) (set! balance (- 25 amount)) 25) 20)   Now we apply the operator by substituting 20 for amount  in the body of the lambda  expression: (set! balance (- 25 20)) 25   If we adhered to the substitution model, we would have to say that the meaning of the procedure application is to first set balance  to 5 and then return 25 as the value of the expression. This gets the wrong answer . In order to get the correct answer , we would have to somehow distinguish the first occurrence of balance  (before the ef fect of the se


key: chroma:document
string_value:
t! ) from the second occurrence of balance  (after the ef fect of the set! ), and the substitution model cannot do this. The trouble here is that substitution is based ultimately on the notion that the symbols in our language are essentially names for values. But as soon as we introduce set!  and the idea that the value of a variable can change, a variable can no longer be simply a name. Now a variable somehow refers to a place where a value can be stored, and the value stored at this place can change. In section 3.2 we will see how environments play this role of “place” in our computational model. Sameness and change The issue surfacing here is more profound than the mere breakdown of a particular model of computation. As soon as we introduce change into our computational models, many notions that were previously straightforward become problematical. Consider the concept of two things being “the same.” Suppose we call make-decrementer  twice with the same ar gument to create two procedures: (define D1 (make-


key: chroma:document
string_value:
decrementer 25))   (define D2 (make-decrementer 25))  Are D1 and D2 the same? An acceptable answer is yes, because D1 and D2 have the same computational behavior – each is a procedure that subtracts its input from 25. In fact, D1 could be substituted for D2 in any computation without changing the result. Contrast this with making two calls to make-simplified-withdraw : (define W1 (make-simplified-withdraw 25))   (define W2 (make-simplified-withdraw 25))   Are W1 and W2 the same? Surely not, because calls to W1 and W2 have distinct effects, as shown by the following sequence of interactions: (W1 20)  5  (W1 20)  - 15  (W2 20)  5  Even though W1 and W2 are “equal” in the sense that they are both created by evaluating the same expression, (make-simplified-withdraw 25) , it is not true that W1 could be substituted for W2 in any expression without changing the result of evaluating the expression. A language that supports the concept that “equals can be substituted for equals” in an expresssion without changing the


key: chroma:document
string_value:
 value of the expression is said to be referentially transpar ent. Referential transparency is violated when we include set!  in our computer language. This makes it tricky to determine when we can simplify expressions by substituting equivalent expressions. Consequently , reasoning about programs that use assignment becomes drastically more dif ficult. Once we for go referential transparency , the notion of what it means for computational objects to be “the same” becomes dif ficult to capture in a formal way . Indeed, the meaning of “same” in the real world that our programs model is hardly clear in itself. In general, we can determine that two apparently identical objects are indeed “the same one” only bymodifying one object and then observing whether the other object has changed in the same way . But how can we tell if an object has “changed” other than by observing the “same” object twice and seeing whether some property of the object dif fers from one observation to the next? Thus, we cannot determine “c


key: chroma:document
string_value:
hange” without some a priori  notion of “sameness,” and we cannot determine sameness without observing the ef fects of change. As an example of how this issue arises in programming, consider the situation where Peter and Paul have a bank account with $100 in it. There is a substantial dif ference between modeling this as (define peter-acc (make-account 100))   (define paul-acc (make-account 100))   and modeling it as (define peter-acc (make-account 100))   (define paul-acc peter-acc)   In the first situation, the two bank accounts are distinct. Transactions made by Peter will not af fect Paul's account, and vice versa. In the second situation, however , we have defined paul-acc  to be the same thing  as peter-acc . In effect, Peter and Paul now have a joint bank account, and if Peter makes a withdrawal from peter-acc  Paul will observe less money in paul-acc . These two similar but distinct situations can cause confusion in building computational models. With the shared account, in particular , it can be espe


key: chroma:document
string_value:
cially confusing that there is one object (the bank account) that has two dif ferent names ( peter-acc  and paul-acc ); if we are searching for all the places in our program where paul-acc  can be changed, we must remember to look also at things that change peter-acc .10 With reference to the above remarks on “sameness” and “change,” observe that if Peter and Paul could only examine their bank balances, and could not perform operations that changed the balance, then the issue of whether the two accounts are distinct would be moot. In general, so long as we never modify data objects, we can regard a compound data object to be precisely the totality of its pieces. For example, a rational number is determined by giving its numerator and its denominator . But this view is no longer valid inthe presence of change, where a compound data object has an “identity” that is something dif ferent from the pieces of which it is composed. A bank account is still “the same” bank account even if we change the balance by makin


key: chroma:document
string_value:
g a withdrawal; conversely , we could have two dif ferent bank accounts with the same state information. This complication is a consequence, not of our programming language, but of our perception of a bank account as an object. We do not, for example, ordinarily regard a rational number as a changeable object with identity , such that we could change the numerator and still have “the same” rational number . Pitfalls of imperative pr ogramming In contrast to functional programming, programming that makes extensive use of assignment is known as imperative pr ogramming . In addition to raising complications about computational models, programs written in imperative style are susceptible to bugs that cannot occur in functional programs. For example, recall the iterative factorial program from section 1.2.1 : (define (factorial n)     (define (iter product counter)       (if (> counter n)           product           (iter (* counter product)                 (+ counter 1))))     (iter 1 1))   Instead of passing ar 


key: chroma:document
string_value:
guments in the internal iterative loop, we could adopt a more imperative style by using explicit assignment to update the values of the variables product  and counter : (define (factorial n)     (let ((product 1)           (counter 1))       (define (iter)         (if (> counter n)             product            (begin (set! product (* counter product))                    (set! counter (+ counter 1))                    (iter))))       (iter)))   This does not change the results produced by the program, but it does introduce a subtle trap. How do we decide the order of the assignments? As it happens, the program is correct as written. But writing the assignments in the opposite order (set! counter (+ counter 1))   (set! product (* counter product))   would have produced a dif ferent, incorrect result. In general, programming with assignment forces us to carefully consider the relative orders of the assignments to make sure that each statement is using the correct version of the variables that have been changed


key: chroma:document
string_value:
. This issue simply does not arise in functional programs.11 The complexity of imperative programs becomes even worse if we consider applications in which several processes execute concurrently . We will return to this in section 3.4. First, however , we will address the issue of providing a computational model for expressions that involve assignment, and explore the uses of objects with local state in designing simulations. Exer cise 3.7.   Consider the bank account objects created by make-account , with the password modification described in exercise 3.3. Suppose that our banking system requires the ability to make joint accounts. Define a procedure make-joint  that accomplishes this. Make-joint  should take three arguments. The first is a password-protected account. The second ar gument must match the password with which the account was defined in order for the make-joint  operation to proceed. The third ar gument is a new password. Make-joint  is to create an additional access to the original account usin


key: chroma:document
string_value:
g the new password. For example, if peter-acc  is a bank account with password open-sesame , then (define paul-acc     (make-joint peter-acc 'open-sesame 'rosebud))  will allow one to make transactions on peter-acc  using the name paul-acc and the password rosebud . You may wish to modify your solution to exercise 3.3 to accommodate this new feature. Exer cise 3.8.   When we defined the evaluation model in section 1.1.3 , we said that the first step in evaluating an expression is to evaluate its subexpressions. But we never specified the order in which the subexpressions should be evaluated (e.g., left to right or right to left). When we introduce assignment, the order in which the ar guments to a procedure are evaluated can make a dif ference to the result. Define a simple procedure f such that evaluating (+ (f 0) (f 1))  will return 0 if the ar guments to + are evaluated from left to right but will return 1 if the ar guments are evaluated from right to left. 1 Actually , this is not quite true. One exceptio


key: chroma:document
string_value:
n was the random-number generator in section 1.2.6 . Another exception involved the operation/type tables we introduced in section 2.4.3 , where the values of two calls to get with the same ar guments depended on intervening calls to put. On the other hand, until we introduce assignment, we have no way to create such procedures ourselves. 2 The value of a set!  expression is implementation-dependent. Set!  should be used only for its effect, not for its value. The name set!  reflects a naming convention used in Scheme: Operations that change the values of variables (or that change data structures, as we will see in section 3.3) are given names that end with an exclamation point. This is similar to the convention of designating predicates by names that end with a question mark. 3 We have already used begin  implicitly in our programs, because in Scheme the body of a procedure can be a sequence of expressions. Also, the < consequent > part of each clause in a cond  expression can be a sequence of expressions ra


key: chroma:document
string_value:
ther than a single expression. 4 In programming-language jar gon, the variable balance  is said to be encapsulated  within the new- withdraw  procedure. Encapsulation reflects the general system-design principle known as the hiding principle : One can make a system more modular and robust by protecting parts of the system from each other; that is, by providing information access only to those parts of the system that have a “need to know .” 5 In contrast with new-withdraw  above, we do not have to use let to make balance  a local variable, since formal parameters are already local. This will be clearer after the discussion of the environment model of evaluation in section 3.2. (See also exercise 3.10.) 6 One common way to implement rand-update  is to use the rule that x is updated to ax + b modulo m, where a, b, and m are appropriately chosen integers. Chapter 3 of Knuth 1981 includes an extensive discussion of techniques for generating sequences of random numbers and establishingtheir statistical properties.


key: chroma:document
string_value:
 Notice that the rand-update  procedure computes a mathematical function: Given the same input twice, it produces the same output. Therefore, the number sequence produced by rand-update  certainly is not “random,” if by “random” we insist that each number in the sequence is unrelated to the preceding number . The relation between “real randomness” and so-called pseudo-random  sequences, which are produced by well-determined computations and yet have suitable statistical properties, is a complex question involving dif ficult issues in mathematics and philosophy . Kolmogorov , Solomonof f, and Chaitin have made great progress in clarifying these issues; a discussion can be found in Chaitin 1975. 7 This theorem is due to E. Cesàro. See section 4.5.2 of Knuth 1981 for a discussion and a proof. 8 MIT Scheme provides such a procedure. If random  is given an exact integer (as in section 1.2.6 ) it returns an exact integer , but if it is given a decimal value (as in this exercise) it returns a decimal value. 9 We don


key: chroma:document
string_value:
't substitute for the occurrence of balance  in the set!  expression because the < name > in a set!  is not evaluated. If we did substitute for it, we would get (set! 25 (- 25 amount)) , which makes no sense. 10 The phenomenon of a single computational object being accessed by more than one name is known as aliasing . The joint bank account situation illustrates a very simple example of an alias. In section 3.3 we will see much more complex examples, such as “distinct” compound data structures that share parts. Bugs can occur in our programs if we for get that a change to an object may also, as a “side ef fect,” change a “dif ferent” object because the two “dif ferent” objects are actually a single object appearing under dif ferent aliases. These so-called side-effect bugs  are so dif ficult to locate and to analyze that some people have proposed that programming languages be designed in such a way as to not allow side ef fects or aliasing (Lampson et al. 1981; Morris, Schmidt, and W adler 1980). 11 In view o


key: chroma:document
string_value:
f this, it is ironic that introductory programming is most often taught in a highly imperative style. This may be a vestige of a belief, common throughout the 1960s and 1970s, that programs that call procedures must inherently be less ef ficient than programs that perform assignments. (Steele (1977) debunks this ar gument.) Alternatively it may reflect a view that step-by- step assignment is easier for beginners to visualize than procedure call. Whatever the reason, it often saddles beginning programmers with “should I set this variable before or after that one” concerns that can complicate programming and obscure the important ideas.3.2  The Environment Model of Evaluation When we introduced compound procedures in chapter 1, we used the substitution model of evaluation (section 1.1.5 ) to define what is meant by applying a procedure to ar guments: To apply a compound procedure to ar guments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument. Once we admit as


key: chroma:document
string_value:
signment into our programming language, such a definition is no longer adequate. In particular , section 3.1.3  argued that, in the presence of assignment, a variable can no longer be considered to be merely a name for a value. Rather , a variable must somehow designate a “place” in which values can be stored. In our new model of evaluation, these places will be maintained in structures called envir onments . An environment is a sequence of frames . Each frame is a table (possibly empty) of bindings , which associate variable names with their corresponding values. (A single frame may contain at most one binding for any variable.) Each frame also has a pointer to its enclosing envir onment , unless, for the purposes of discussion, the frame is considered to be global . The value of a variable  with respect to an environment is the value given by the binding of the variable in the first frame in the environment that contains a binding for that variable. If no frame in the sequence specifies a binding for the va


key: chroma:document
string_value:
riable, then the variable is said to be unbound  in the environment. Figur e 3.1:   A simple environment structure.Figure 3.1 shows a simple environment structure consisting of three frames, labeled I, II, and III. In the diagram, A, B, C, and D are pointers to environments. C and D point to the same environment. The variables z and x are bound in frame II, while y and x are bound in frame I. The value of x in environment D is 3. The value of x with respect to environment B is also 3. This is determined as follows: W e examine the first frame in the sequence (frame III) and do not find a binding for x, so we proceed to the enclosing environment D and find the binding in frame I. On the other hand, the value of x in environment A is 7, because the first frame in the sequence (frame II) contains a binding of x to 7. With respect to environment A, the binding of x to 7 in frame II is said to shadow  the binding of x to 3 in frame I. The environment is crucial to the evaluation process, because it determines the 


key: chroma:document
string_value:
context in which an expression should be evaluated. Indeed, one could say that expressions in a programming language do not, in themselves, have any meaning. Rather , an expression acquires a meaning only with respect to some environment in which it is evaluated. Even the interpretation of an expression as straightforward as (+ 1 1)  depends on an understanding that one is operating in a context in which + is the symbol for addition. Thus, in our model of evaluation we will always speak of evaluating an expression with respect to some environment. To describe interactions with the interpreter , we will suppose that there is a global environment, consisting of a single frame (with no enclosing environment) that includes values for the symbols associated with the primitive procedures. For example, the idea that + is the symbol for addition is captured by saying that the symbol + is bound in the global environment to the primitive addition procedure. 3.2.1  The Rules for Evaluation The overall specification of h


key: chroma:document
string_value:
ow the interpreter evaluates a combination remains the same as when we first introduced it in section 1.1.3 : To evaluate a combination: 1.  Evaluate the subexpressions of the combination.122.  Apply the value of the operator subexpression to the values of the operand subexpressions. The environment model of evaluation replaces the substitution model in specifying what it means to apply a compound procedure to ar guments. In the environment model of evaluation, a procedure is always a pair consisting of some code and a pointer to an environment. Procedures are created in one way only: by evaluating a lambda  expression. This produces a procedure whose code is obtained from the text of the lambda  expression and whose environment is the environment in which the lambda  expression was evaluated to produce the procedure. For example, consider the procedure definition (define (square x)     (* x x))   evaluated in the global environment. The procedure definition syntax is just syntactic sugar for an underlying im


key: chroma:document
string_value:
plicit lambda  expression. It would have been equivalent to have used (define square     (lambda (x) (* x x)))   which evaluates (lambda (x) (* x x))  and binds square  to the resulting value, all in the global environment. Figure 3.2 shows the result of evaluating this define  expression. The procedure object is a pair whose code specifies that the procedure has one formal parameter , namely x, and a procedure body (* x x) . The environment part of the procedure is a pointer to the global environment, since that is the environment in which the lambda  expression was evaluated to produce the procedure. A new binding, which associates the procedure object with the symbol square , has been added to the global frame. In general, define  creates definitions by adding bindings to frames. Figur e 3.2:   Environment structure produced by evaluating (define (square x) (* x x))  in the global environment.Now that we have seen how procedures are created, we can describe how procedures are applied. The environment model


key: chroma:document
string_value:
 specifies: T o apply a procedure to ar guments, create a new environment containing a frame that binds the parameters to the values of the ar guments. The enclosing environment of this frame is the environment specified by the procedure. Now , within this new environment, evaluate the procedure body . To show how this rule is followed, figure 3.3 illustrates the environment structure created by evaluating the expression (square 5)  in the global environment, where square  is the procedure generated in figure 3.2. Applying the procedure results in the creation of a new environment, labeled E1 in the figure, that begins with a frame in which x, the formal parameter for the procedure, is bound to the ar gument 5. The pointer leading upward from this frame shows that the frame's enclosing environment is the global environment. The global environment is chosen here, because this is the environment that is indicated as part of the square procedure object. Within E1, we evaluate the body of the procedure, (* x x). 


key: chroma:document
string_value:
Since the value of x in E1 is 5, the result is (* 5 5) , or 25. Figur e 3.3:   Environment created by evaluating (square 5)  in the global environment.The environment model of procedure application can be summarized by two rules: A procedure object is applied to a set of ar guments by constructing a frame, binding the formal parameters of the procedure to the arguments of the call, and then evaluating the body of the procedure in the context of the new environment constructed. The new frame has as its enclosing environment the environment part of the procedure object being applied. A procedure is created by evaluating a lambda  expression relative to a given environment. The resulting procedure object is a pair consisting of the text of the lambda  expression and a pointer to the environment in which the procedure was created. We also specify that defining a symbol using define  creates a binding in the current environment frame and assigns to the symbol the indicated value.13 Finally , we specify the behavio


key: chroma:document
string_value:
r of set! , the operation that forced us to introduce the environment model in the first place. Evaluating the expression (set! <variable > <value>) in some environment locates the binding of the variable in the environment and changes that binding to indicate the new value. That is, one finds the first frame in the environment that contains a binding for the variable and modifies that frame. If the variable is unbound in the environment, then set!  signals an error . These evaluation rules, though considerably more complex than the substitution model, are still reasonably straightforward. Moreover , the evaluation model, though abstract, provides a correct description of how the interpreter evaluates expressions. In chapter 4 we shall see how this model can serve as a blueprint for implementing a working interpreter . The following sections elaborate the details of the model by analyzing some illustrative programs. 3.2.2  Applying Simple Pr ocedur es When we introduced the substitution model in section 1.1.5


key: chroma:document
string_value:
  we showed how the combination (f 5)  evaluates to 136, given the following proceduredefinitions: (define (square x)     (* x x))   (define (sum-of-squares x y)     (+ (square x) (square y)))   (define (f a)     (sum-of-squares (+ a 1) (* a 2)))   We can analyze the same example using the environment model. Figure 3.4 shows the three procedure objects created by evaluating the definitions of f, square , and sum-of-squares  in the global environment. Each procedure object consists of some code, together with a pointer to the global environment. Figur e 3.4:   Procedure objects in the global frame. In figure 3.5 we see the environment structure created by evaluating the expression (f 5) . The call to f creates a new environment E1 beginning with a frame in which a, the formal parameter of f, is bound to the argument 5. In E1, we evaluate the body of f: (sum-of-squares (+ a 1) (* a 2))   Figur e 3.5:   Environments created by evaluating (f 5)  using the procedures in figure 3.4.To evaluate this combination, we 


key: chroma:document
string_value:
first evaluate the subexpressions. The first subexpression, sum-of-squares , has a value that is a procedure object. (Notice how this value is found: W e first look in the first frame of E1, which contains no binding for sum-of-squares . Then we proceed to the enclosing environment, i.e. the global environment, and find the binding shown in figure 3.4.) The other two subexpressions are evaluated by applying the primitive operations + and * to evaluate the two combinations (+ a 1)  and (* a 2)  to obtain 6 and 10, respectively . Now we apply the procedure object sum-of-squares  to the ar guments 6 and 10. This results in a new environment E2 in which the formal parameters x and y are bound to the ar guments. Within E2 we evaluate the combination (+ (square x) (square y)) . This leads us to evaluate (square x) , where square  is found in the global frame and x is 6. Once again, we set up a new environment, E3, in which x is bound to 6, and within this we evaluate the body of square , which is (* x x) . Also as 


key: chroma:document
string_value:
part of applying sum-of-squares , we must evaluate the subexpression (square y), where y is 10. This second call to square  creates another environment, E4, in which x, the formal parameter of square , is bound to 10. And within E4 we must evaluate (* x x) . The important point to observe is that each call to square  creates a new environment containing a binding for x. We can see here how the dif ferent frames serve to keep separate the dif ferent local variables all named x. Notice that each frame created by square  points to the global environment, since this is the environment indicated by the square  procedure object.After the subexpressions are evaluated, the results are returned. The values generated by the two calls to square  are added by sum-of-squares , and this result is returned by f. Since our focus here is on the environment structures, we will not dwell on how these returned values are passed from call to call; however , this is also an important aspect of the evaluation process, and we will r


key: chroma:document
string_value:
eturn to it in detail in chapter 5. Exer cise 3.9.   In section 1.2.1  we used the substitution model to analyze two procedures for computing factorials, a recursive version (define (factorial n)     (if (= n 1)         1        (* n (factorial (- n 1)))))   and an iterative version (define (factorial n)     (fact-iter 1 1 n))   (define (fact-iter product counter max-count)     (if (> counter max-count)         product         (fact-iter (* counter product)                    (+ counter 1)                    max-count)))   Show the environment structures created by evaluating (factorial 6) using each version of the factorial  procedure.14 3.2.3  Frames as the Repository of Local State We can turn to the environment model to see how procedures and assignment can be used to represent objects with local state. As an example, consider the “withdrawal processor” from section 3.1.1  created by calling the procedure (define (make-withdraw balance)     (lambda (amount)      (if (>= balance amount)           (begin (s


key: chroma:document
string_value:
et! balance (- balance amount))                  balance)           "Insufficient funds")))   Let us describe the evaluation of (define W1 (make-withdraw 100))   followed by (W1 50)  50  Figure 3.6 shows the result of defining the make-withdraw  procedure in the global environment. This produces a procedure object that contains a pointer to the global environment. So far , this is no dif ferent from the examples we have already seen, except that the body of the procedure is itself a lambda  expression. Figur e 3.6:   Result of defining make-withdraw  in the global environment. The interesting part of the computation happens when we apply the procedure make-withdraw  to an ar gument: (define W1 (make-withdraw 100))  We begin, as usual, by setting up an environment E1 in which the formal parameter balance  is bound to the ar gument 100. Within this environment, we evaluate the body of make-withdraw , namely the lambda  expression. This constructs a new procedure object, whose code is as specified by the lambda 


key: chroma:document
string_value:
 and whose environment is E1, the environment in which the lambda was evaluated to produce the procedure. The resulting procedure object is the value returned by the call to make-withdraw . This is bound to W1 in the global environment, since the define  itself is being evaluated in the global environment. Figure 3.7 shows the resulting environment structure. Figur e 3.7:   Result of evaluating (define W1 (make-withdraw 100)) . Now we can analyze what happens when W1 is applied to an ar gument: (W1 50)  50  We begin by constructing a frame in which amount , the formal parameter of W1, is bound to the ar gument 50. The crucial point to observe is that this frame has as its enclosing environment not the global environment, but rather the environment E1, because this is the environment that is specified by the W1 procedure object. Within this new environment, we evaluate the body of the procedure: (if (>= balance amount)       (begin (set! balance (- balance amount))             balance)       "Insufficient fund


key: chroma:document
string_value:
s")   The resulting environment structure is shown in figure 3.8. The expression being evaluated references both amount  and balance . Amount  will be found in the first frame in the environment, while balance  will be found by following the enclosing-environment pointer to E1. Figur e 3.8:   Environments created by applying the procedure object W1. When the set!  is executed, the binding of balance  in E1 is changed. At the completion of the call to W1, balance  is 50, and the frame that contains balance  is still pointed to by the procedure object W1. The frame that binds amount  (in which we executed the code that changed balance ) is no longer relevant, since the procedure call that constructed it has terminated, and there are no pointers to that frame from other parts of the environment. The next time W1 is called, this will build a new frame that binds amount  and whose enclosing environment is E1. We see that E1 serves as the “place” that holds the local state variable for the procedure object W1. Figu


key: chroma:document
string_value:
re 3.9 shows the situation after the call to W1. Figur e 3.9:   Environments after the call to W1.Observe what happens when we create a second “withdraw” object by making another call to make-withdraw : (define W2 (make-withdraw 100))   This produces the environment structure of figure 3.10, which shows that W2 is a procedure object, that is, a pair with some code and an environment. The environment E2 for W2 was created by the call to make-withdraw . It contains a frame with its own local binding for balance . On the other hand, W1 and W2 have the same code: the code specified by the lambda  expression in the body of make-withdraw .15 We see here why W1 and W2 behave as independent objects. Calls to W1 reference the state variable balance  stored in E1, whereas calls to W2 reference the balance  stored in E2. Thus, changes to the local state of one object do not af fect the other object. Figur e 3.10:   Using (define W2 (make-withdraw 100))  to create a second object.Exer cise 3.10.   In the make-withdraw  p


key: chroma:document
string_value:
rocedure, the local variable balance is created as a parameter of make-withdraw . We could also create the local state variable explicitly , using let, as follows: (define (make-withdraw initial-amount)     (let ((balance initial-amount))       (lambda (amount)         (if (>= balance amount)             (begin (set! balance (- balance amount))                    balance)             "Insufficient funds"))))   Recall from section 1.3.2  that let is simply syntactic sugar for a procedure call: (let ((< var> <exp>)) <body>)  is interpreted as an alternate syntax for ((lambda (< var>) <body>) <exp>)  Use the environment model to analyze this alternate version of make- withdraw , drawing figures like the ones above to illustrate the interactions (define W1 (make-withdraw 100))     (W1 50)    (define W2 (make-withdraw 100))   Show that the two versions of make-withdraw  create objects with the same behavior . How do the environment structures dif fer for the two versions? 3.2.4  Internal Definitions Section 1.1.8 


key: chroma:document
string_value:
 introduced the idea that procedures can have internal definitions, thus leading to a block structure as in the following procedure to compute square roots:(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))   Now we can use the environment model to see why these internal definitions behave as desired. Figure 3.11 shows the point in the evaluation of the expression (sqrt 2)  where the internal procedure good-enough?  has been called for the first time with guess  equal to 1. Figur e 3.1 1:  Sqrt  procedure with internal definitions. Observe the structure of the environment. Sqrt  is a symbol in the global environment that is bound to a procedure object whose associated environment is the global environment. When sqrt  was called, a new environment E1 was formed, subordin


key: chroma:document
string_value:
ate to the global environment, in which the parameter x is bound to 2. The body of sqrt  was then evaluated in E1. Since the first expression in the body of sqrt  is(define (good-enough? guess)     (< (abs (- (square guess) x)) 0.001))   evaluating this expression defined the procedure good-enough?  in the environment E1. To be more precise, the symbol good-enough?  was added to the first frame of E1, bound to a procedure object whose associated environment is E1. Similarly , improve  and sqrt-iter  were defined as procedures in E1. For conciseness, figure 3.11 shows only the procedure object for good-enough? . After the local procedures were defined, the expression (sqrt-iter 1.0) was evaluated, still in environment E1. So the procedure object bound to sqrt-iter  in E1 was called with 1 as an ar gument. This created an environment E2 in which guess , the parameter of sqrt-iter , is bound to 1. Sqrt-iter  in turn called good-enough?  with the value of guess  (from E2) as the ar gument for good-enough? . This 


key: chroma:document
string_value:
set up another environment, E3, in which guess  (the parameter of good-enough? ) is bound to 1. Although sqrt-iter  and good-enough?  both have a parameter named guess , these are two distinct local variables located in dif ferent frames. Also, E2 and E3 both have E1 as their enclosing environment, because the sqrt-iter  and good-enough?  procedures both have E1 as their environment part. One consequence of this is that the symbol x that appears in the body of good- enough?  will reference the binding of x that appears in E1, namely the value of x with which the original sqrt  procedure was called. The environment model thus explains the two key properties that make local procedure definitions a useful technique for modularizing programs: The names of the local procedures do not interfere with names external to the enclosing procedure, because the local procedure names will be bound in the frame that the procedure creates when it is run, rather than being bound in the global environment. The local procedures 


key: chroma:document
string_value:
can access the ar guments of the enclosing procedure, simply by using parameter names as free variables. This is because the body of the local procedure is evaluated in an environment that is subordinate to the evaluation environment for the enclosing procedure.Exer cise 3.1 1.  In section 3.2.3  we saw how the environment model described the behavior of procedures with local state. Now we have seen how internal definitions work. A typical message-passing procedure contains both of these aspects. Consider the bank account procedure of section 3.1.1 : (define (make-account balance)     (define (withdraw amount)       (if (>= balance amount)           (begin (set! balance (- balance amount))                  balance)           "Insufficient funds"))     (define (deposit amount)       (set! balance (+ balance amount))       balance)     (define (dispatch m)       (cond ((eq? m 'withdraw) withdraw)             ((eq? m 'deposit) deposit)             (else (error "Unknown request -- MAKE-ACCOUNT"                   


key: chroma:document
string_value:
       m))))     dispatch)   Show the environment structure generated by the sequence of interactions (define acc (make-account 50))     ((acc 'deposit) 40)   90    ((acc 'withdraw) 60)   30  Where is the local state for acc kept? Suppose we define another account (define acc2 (make-account 100))   How are the local states for the two accounts kept distinct? Which parts of the environment structure are shared between acc and acc2 ?12 Assignment introduces a subtlety into step 1 of the evaluation rule. As shown in exercise 3.8, the presence of assignment allows us to write expressions that will produce dif ferent values depending on the order in which the subexpressions in a combination are evaluated. Thus, to be precise, we should specify an evaluation order in step 1 (e.g., left to right or right to left). However , this order should always be considered to be an implementation detail, and one should never write programs that depend on some particular order . For instance, a sophisticated compiler might opti


