SICP Exercises
Chapter 02 - Part.III
Exercise 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 that the problem is that the complex-number selectors were never defined
for complex numbers, just for polar and 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) (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?
- Answer:
When the interpreter try to evaluate (magnitude z)
, where z
is the date
object '(complex . (rectangular . (3 4)))
, the magnitude
precedure shall get the
tag of this data, which is complex
, then lookup into the selector in dispatch
table, not surprisingly, this will fail, for there is not such thing. Then, the
dispatcher apply-generic
just returns an error.
After we install those orecedures in the complex pckage, the lookup will return
magnitude
as the selector. Then apply-generic
will call this precedure like
(magnitude (rectangular . (3 4)))
. Again, this needs to call apply-generic
,
but needs to lookup the selector for the tag rectangular
, and it is
magnitude
in rectangular-package
, and then the interpreter shall evaluate
(magnitude 3 4)
and returns the right answer 5
.
Exercise 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 as before
except that ordinary numbers should be represented simply as Scheme numbers
rather than as pairs whose car
is the symbol scheme-number
.
- Answer:
(define (type-tag data) (cond ((pair? data) (let ((tag (car data))) (if (symbol? tag) tag 'scheme-pair))) ((number? data) 'scheme-number) (else "error tag -- DATA" data))) ; list of data types with no data tags (define internal-list '(scheme-number scheme-pair)) (define (attach-tag tag data) (if (any (lambda (x) (eq? tag x)) internal-list) data (cons tag data))) (define (content data) (let ((tag (type-tag data))) (if (any (lambda (x) (eq? tag x)) internal-list) data (cdr data)))) ; then, we can add those type like (put 'add '(scheme-number scheme-number) +) ; or the more "unified" way, attaching tags (put 'add '(scheme-number scheme-number) (lambda (x y) (attach-tag 'scheme-number (+ x y))))
Exercise 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.
- Answer:
; this goes into scheme-number package (put 'equ? '(scheme-number scheme-number) (lambda (x y) (= x y))) ; and the retional number (put 'equ? '(rational rational) (lambda (x y) (= (* (numer x) (denom y)) (* (denom x) (numer y))))) ; and complex number (put 'equ? '(complex complex) (lambda (x y) (and (= (real-part x) (real-part y)) (= (imag-part x) (imag-part y)))))
Exercise 2.80
Define a generic predicate =zero?
that tests if its argument is zero, and
install it in the generic arithmetic package. This operation should work for
ordinary numbers, rational numbers, and complex numbers.
(put '=zero? 'scheme-number (lambda (x) (= x 0))) (put '=zero? 'rational (lambda (x) (and (= 0 (numer x)) (not (= 0 (denom x)))))) (put '=zero? 'complex (lambda (x) (= 0 (magnitude x))))
Exercise 2.81
Louis Reasoner has noticed that apply-generic
may try to coerce the arguments to
each other's type even if they already have the same type. Therefore, he
reasons, we need to put procedures in the coercion table to "coerce" arguments
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)
-
With Louis's coercion procedures installed, what happens if apply-generic is
called with two arguments 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 complex numbers as arguments? - 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?
- Modify apply-generic so that it doesn't try coercion if the two arguments have the same type.
- Answer:
Louis's coercion precedures is a bad idea. When calling a precedure cannot found
in the dispatch table, the apply-generic
will start try to coerce both args,
form themself to themself, and call apple-generic
and doing all this all
over again. The evaluation will trapped in an infinity loop, with no exit. In
fact, what Louis did is pointless, but reveal a possible bug here: if we happen
to have the coercion between in the corecion table, and call a apply-generic
with no generic precedure in the dispatch table, this will end up with infinate
loop.
To make sure that this never happen, we needs to check if the args is in a same type:
(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)) (if (= (length args) 2) (let ((type1 (car type-tags)) (type2 (cadr type-tags)) (a1 (car args)) (a2 (cadr args))) ; ==== NEW CODE HERE ==== (if (eq? type1 type2) (error "No method for these type" (list op type-tags)) (let ((t1->t2 (get-coercion type1 type2)) (t)'>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)))))))
But this method has a big problem: when we only have a precedure that only
handles data with different type, implement apply-generic
in this fashion will
never let you do what you want.
Exercise 2.82
Show how to generalize apply-generic
to handle coercion in the general case of
multiple arguments. One strategy is to attempt to coerce all the arguments to
the type of the first argument, then to the type of the second argument, and so
on. Give an example of a situation where this strategy (and likewise the
two-argument 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.)
- Answer:
This modified apply-generic
can be apply to multiple args:
(define (apply-generic op . args) (let ((type-tags (map type-tag args))) (define errmsg (error "No method for those types" (list op type-tags))) ; no conversion (define (id x) x) ; when same type conversion, retuen precedure id (define (get-coerce t1 t2) (if (eq? t1 t2) id (get-corection t1 t2))) ; helper function for get coercion for all types (define (first-coercion rest-types) (let ((t (cat rest-types))) (if (null? t) '() ; no coercion function for all types (let ((coerce-list (map (lambda (t) (get-coercion t this-type) type-tags)))) (if (all id coerce-list) coerce-list (first-coercion (cdr rest-types))))))) ; same to (zipWith $) in Haskell (define (zipwith$ funclist arglist) (define (iter fs as result) (if (or (nil? fs) (nil? as)) (reverse result) (iter (cdr fs) (cdr as) (cons ((car fs) (car zs)) resule)))) (iter fs as '())) (let ((proc (get op type-tags))) (if proc (apply proc (map contents args)) (cond ((= 1 (length args)) (errmsg)) ((all (lambda (x) (eq? x (car type-tags))) (cdr args)) (errmsg)) (else (let ((coercions (first-coercion type-tags))) (if (coercions) (apply proc (zipwith$ coercions args)) (errmsg)))))))))
Very complex, yet buggy one. Consider the following situation: if there is no
precedure to coerce one type directly into another, say, we have precedure to
coerce integer
to rational
and rational
to real
, but we do not have a precedure
to coerce integer
to real
in one step, then this precedure will not try the
two step coerce.
Exercise 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 objects of that type one level
in t)e tower. Show how to install a generic raise operation that will work for
each type (except complex).
- Answer:
Supporsed we already have those type coercion precedures to coerce one type up
along the tree on level, all we need to do is to install generic operation
raise
to dispatch table, like any other generic operations.
(put 'raise 'integer (lambda (i) ((get-coeration 'integer 'rational) i))) (put 'raise 'rational (lambda (r) ((get-coeration 'rational 'real) r))) (put 'raise 'real (lambda (r) ((get-coeration 'real 'complex) r))) ; and the generic operation (define (raise x) (apply-generic 'raise x))
Exercise 2.84
Using the raise
operation of exercise 2.83, modify the apply-generic
procedure
so that it coerces its arguments 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.
- Answer:
; for simplicity, only implement apply-generic with two args (define (apply-generic op v1 v2) ; raise v to the type t (define (raise-to v t) (let ((vt (type-tag v))) (let ((raise-proc (get 'raise vt))) (cond ((eq? vt t) v) (raise-proc (raise-to (raise-proc v) t)) (else #f))))) (let ((t1 (type-tag v1)) (t2 (type-tag v2))) (let ((proc (get op (list t1 t1)))) (cond (proc (proc v1 v2)) ((eq? t1 t2) (error "NO precedure for" (list op t1 t2))) (else (let ((v1->v2 (raise-to v1 t2))) (if v1->v2 (apply-generic op v1->v2 v2) (let ((v2->v1 (raise v2 v1))) (if v2->v1 (apply-generic op v1 v2->v1) (error "No precedure for" (list op t1 t2)))))))))))
Exercise 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 + 0i
can be lowered as far as real
, the complex
number 1 + 0i
can be lowered as far as integer
, and the complex
number 2 + 3i
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 operations 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.
- Answer:
Again, we do not implement each drop one level precedures, assume they are
already implemented by the convention of returning the same value when cannot
simplify the value. For example trying to drop 1+2i
will get 1+2i
.
(put 'drop 'complex drop-complex) (put 'drop 'real drop-real) (put 'drop 'rational drop-rational) (put 'drop 'integer (lambda (x) x)) ; basic level, cannot drop ; generic drop precedure (define (drop x) (let ((tag (type-tag x))) (dropped-value (apply-generic 'drop x)) (let ((droppen-tag (type-tag dropped-value))) (if (eq? tag dropped-tag) x (drop dropped-value))))) ; the new version of apply-generic only needs to drop the return value. ; apply-generic-2-84 is the apply-generic in exercise 2.84 (define (apply-generic op v1 v2) (drop (apply-generic-2-84 p v1 v2)))
Exercise 2.86
Suppose we want to handle complex
numbers whose real parts, imaginary parts,
magnitudes, and angles can be either 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.
- Answer:
Only partialy implement integer
package.
(define (install-integer-package) (define (tag x) (attach-tag 'integer x)) (put 'make 'integer (lambda(x) (tag x))) ; don't install cos and sin, calling these precedure will result in a raise (put 'add 'integer (lambda (x y) (tag (+ (int x) (int y))))) ; and other arithmetic operatios... 'done)
To get a more consistant with the real mathematic defination, we needs to define
the sine
precedure in the real
package to implement the "classic" sine
,
whose domain is just all real number, and then define its analytic prolongation
in complex
package, only implementing the complex number part. Calling sine
with an integer
will result in raise
its argument twice into real
, then
calling sine
in real
package.
Exercise 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.
- Answer:
(define (=zero? x) (apply-generic '=zero? x)) ; this goes into the scheme-number package (put '=zero? 'scheme-number (lambda (x) (and (number? x) (= x 0)))) ; and this goes into polynomial package (put '=zero? 'polinomial (lambda (x) (empty-termlist? x)
Exercise 2.88
Extend the polynomial system to include subtraction of polynomials. (Hint: You may find it helpful to define a generic negation operation.)
- Answer:
Just take advantage of the fact \(a+b=a+(-1)*b\):
(define (negation x) (apply-generic 'negation x)) (put 'negation 'polynomial (lambda (x) (make-polynomial (variable x) (map (lambda (t) (make-term (order t) (* -1 (coeff t)))) (term-list t))))) ; define sub via add and negation on generic operation level (define (sub x y) (apply-generic 'add x (apply-generic 'negation y)))
Exercise 2.89
Define procedures that implement the term-list
representation described above as
appropriate for dense polynomials.
- Answer:
Indeed a major change. Starting with the dense list:
(define (install-polynimial-dense-package) ; dealing with tags (define (tag) 'polynomial-dense) (define (tagged x) (appatch-tag tag x)) ; make (put 'make tag (lambda (v t) (tagged (cons v t)))) ; selectors (define (term-list p) (cdr p)) (define (variable p) (car p)) ; helper functions (define (prefix-zeros n l) (if (<= n 0) l (prefix-zeros (- n 1) (cons 0 l)))) (define (zipwith f l1 l2) (define (iter as bs result) (if (or (null? as) (null? bs)) (reverse result) (iter (cdr as) (cdr bs) (cons (f (car as) (car bs)) result))))) ; only add and mulptiply (define (add-termlist p1 p2) (if (not (eq? (variable p1) (variable p2))) (error "Add cannot apply to POLYNOMIAL with different variables") (let ((vl1 (term-list p1)) (vl2 (term-list p2))) (let ((l1 (length vl1)) (l2 (length vl2))) (zipwith add (prefix-zeros (- l2 l1) vl1) (prefox-zeros (- l1 l2) vl2)))))) (put 'add tag add-term) (put 'mul tag (lambda (x y) (define (mul-one t ls) (map (lambda (l) (mul t l)) ls)) (define (iter l result) (if (null? l) result (iter (cdr l) (add-term result (append (mul-one (car l) l2) (prefix-zeros (length l) '())))))) (if (not (eq? (variable p1) (variable p2))) (error "MUL cannot apply to POLYNOMIAL with different variables") (let ((vl1 (term-list p1)) (vl2 (term-list p2))) (let ((l1 (length vl1)) (l2 (length vl2))) (iter l1 l2)))))) ; get termlist as dense list (put 'dense-term 'polynomial-dense term-list) ; get sparse term list (put 'sparse-term 'polynomial-dense (lambda (x) (zipwith make-term-dense (reverse (iterate-interval 0 (length x))) (term-list x)))) 'done)
And the sparse representation should remain as same as polynomial
package in
the book, but rename it to polynimail-sparse
.
Finally, we combine those two representation in a whole package called
polynomial
, and add those generic operations:
(define (install-polynomial-package ))
Exercise 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 different types of term lists and make the operations on term lists generic. Redesign the polynomial system to implement this generalization. This is a major effort, not a local change.
Exercise 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 difference 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 off 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 arguments 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-of-result <compute rest of result recursively> )) <form complete result> ))))))