<< Previous exercise (2.76) | Index | Next exercise (2.78) >>
From exercise: "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 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)
This is acting as a pass-through. If we were only building a complex arithmetic system this would be adding a pointless extra level. But this extra switch is what allows us to combine complex with regular and rational numbers.
;; From text: ;; ----->|.|-------->|.|---------->|.|.| ;; | | | | ;; V V V V ;; complex rectangular 3 4 ;; Figure 2.24: Representation of 3 + 4i in rectangular form. ;; and also from text: (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))
Also from exercise: "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?"
;; Parse error: Closing paren missing.
;;*** Use substitution rule: (magnitude z) ;;** First apply-generic: (apply-generic 'magnitude z) ;; where z is the whole objec including symbol 'complex. ;;recall (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)))))) ;; substitution (let ((type-tags '(complex)) ... )) (let ((proc (get op '(complex))) ... )) (let ((proc magnitude) ... )) (if proc... ;; true (apply magnitude (contents z)) (magnitude z-prime) ;; where z-prime is the contents (the cdr) of the original object, that is, with the 'complex stripped off. ;;** Second apply-generic: (let ((type-tags '(rectangular)) ... )) (let ((proc (get op '(rectangular))) ... )) (let ((proc (get 'magnitude '(rectangular))) ... )) (let ((proc (lambda (z) (sqrt (+ (square (real-part z)) (square (imag-part z)))))) ... ))) (if proc... ;; true (apply (lambda (z) (sqrt (+ (square (real-part z)) (square (imag-part z))))) (contents z-prime)) (sqrt (+ (square 3) (square 4))) 5
Before Alyssa added the code, there is no magnitude operation for complex,hence the error. apply-generic is invoked twice, first dispatch is magnitude of complex, second is magnitude of rectangular.
There is some mistake here. There is no procedure magnitude for the 'complex package, so the first thing failing would be:
(put 'magnitude '(complex) magnitude)
In the code of the book the package does not contain such a procedure magnitude. It seems to assume that Alyssa also writes that code, but in the exercise 2.77 does not say so. The book also does not state in which package the code should be added. The specific procedures magnitude inside the packages rectangular and polar do not work for complex, because they assume, that they are working with either representation and are not aware of the additional tag 'complex attached to the data. So they would extract the wrong pieces from the data and then fail. The implementation of magnitude inside the complex package would probably have to unwrap the tagged data by one tag, the complex tag, and perform a lookup in the table of operations itself:
(define (magnitude z) (get 'magnitude (cadr z) (cdr z))) ;; get operation for magnitude of the data wrapped inside the data. ;; (cadr z) would become 'rectangular ;; (cdr z) would become (list 'rectangular (cons 3 4))
So basically with the information given in the book the tracing (as demanded in the exercise) of the calls would fail (correct me if I am wrong about this please). However, under reasonable assumptions, like a defined magnitude in the package for complex, I think the answer of meteorgan is correct.
Data Structure
Our complex number implementation constructors first call the polar or rectangular constructors, and tag the result with the 'complex tag resulting in the representation found in Figure 2.24.
When Mr. Reasoner called the magnitude function the type-tags call used in apply-generic returned the outer tag of z (complex). Because Mr. Reasoner had not put a magnitude function under the complex tag anywhere in his code this returns an error.
After Louis implements Mrs. Hacker's suggestion to his complex package the apply-generic function will find the magnitude function under the complex tag, and call the magnitude function (again) on the "contents" of the complex number. Which, because z is the object found in Fig. 2.24, is ('rectangular . (3 . 4)). Magnitude will again invoke apply-generic. This time the magnitude function internal to the rectangular-package will be called on (3 . 4).
Apply-generic will be called 2 times and the rectangular-package magnitude function is what is finally called.