2.81 Exercise 2.81
Louis Reasoner wants to install coercions from types to themselves, because apply-generic may try to do this is a procedure isn’t found. Of course, the only reason this would happen is if an operation on the two types couldn’t be found in the first place, and coercing a type to itself won’t change that (this is Loose Reasoner, after all), but let’s play along anyway.
So suppose we have a new procedure for exponentiation only defined on Scheme numbers, created like this:
(define (exp x y) (apply-generic 'exp x y)) (put 'exp '(scheme-number scheme-number) (lambda (x y) (tag (expt x y))))
If we try to call this procedure on two complex numbers, apply-generic won’t be able to find a procedure using get, and so will attempt to coerce the complex numbers to complex numbers and see if there is a procedure then. But looking at how apply-generic is defined, we can see that we don’t have to write these self-coercions:
(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)))))
If we don’t install self-coercions into the table, neither t1->t2 nor t2->t1 will exist, and the call to exp will yield and error as expected. However, if we do install the self-coercions, apply-generic will be called again with one "coerced" argument.
But of course, the next time through apply-generic, the same thing will happen – a suitable procedure won’t be found, and so the first argument will be "coerced" and apply-generic will be called again. Installing these procedures will make apply-generic nonterminating whenever a procedure for two arguments of the same type isn’t found. This is clearly wrong.
We could modify apply-generic to not attempt to look up coercions if the types are the same. It might look something like this:
(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 (and (= (length args) 2) (not (eq? (car type-tags) (cadr type-tags)))) ;; ... (error "No method for these types" (list op type-tags)))))))
You could also check if the types are equal inside the if (conveniently after you have names for them), but I prefer this a little bit because it doesn’t increase the number of exit points in the procedure (that is, I would need to add another call to error).