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)
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?
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
.
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.
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.
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)
a. 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?
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.
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.)
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 the tower. Show how to install a generic raise
operation that will work for each type (except complex).
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.
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.
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.
Install =zero?
for polynomials in the generic arithmetic package. This will allow adjoin-term
to work for polynomials with coefficients that are themselves polynomials.
Extend the polynomial system to include subtraction of polynomials. (Hint: You may find it helpful to define a generic negation operation.)
Define procedures that implement the term-list representation described above as appropriate for dense polynomials.
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.
See page 284.
By imposing an ordering on variables, extend the polynomial package so that addition and multiplication of polynomials works for polynomials in different variables. (This is not easy!)
TODO
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))
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 variable.) 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.