Skip to content

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.

; Source: [https://wizardbook.wordpress.com/2010/12/08/exercise-2-85/](https://wizardbook.wordpress.com/2010/12/08/exercise-2-85/)

; There are 3 changes to make. The first is for apply-generic to use the new drop 
; procedure. Note that it doesn’t make sense to drop the result of all generic 
; operations, for example predicates, and that drop is only called after any type 
; coercion using raise has completed.
(define (apply-generic op . args)

  ; only certain operations will result in an answer that can be
  ; projected e.g. it makes no sense to project the answer to zero?
  (define (reduce-type x)
    (cond ((eq? op 'add) (drop x))
          ((eq? op 'sub) (drop x))
          ((eq? op 'mul) (drop x))
          ((eq? op 'div) (drop x))
          (else x)))

  ; find the highest type level of a list of arguments
  (define (highest-type-level args)
    (if (null? args) 
        0
        (let ((level (type-level (car args)))
              (highest (highest-type-level (cdr args))))
          (if (> level highest)
              level
              highest))))

  ; raise arg to the same level as target-type-level
  (define (raise-to arg target-type-level)
    (define (raise-iter current-arg)   
      (let ((arg-level (type-level current-arg)))
        (cond ((= arg-level target-type-level) current-arg)
              ((< arg-level target-type-level) (raise-iter (apply-generic 'raise current-arg)))
              (else (error "Cannot raise argument to a lower type target" arg target-type-level)))))
    (raise-iter arg))

  ; raise all args to a common type (the highest in the tower of types)
  ; and apply the operator to them 
  (define (apply-with-raised-types args)
    (let ((target-type-level (highest-type-level args)))
      (apply apply-generic 
             op 
             (map (lambda (arg)
                    (raise-to arg target-type-level))
                  args))))

  (let* ((type-tags (map type-tag args))
         (proc (get op type-tags)))
    (if proc 
        (reduce-type (apply proc (map contents args)))
        (apply-with-raised-types args))))

; Second implement drop and the top-level generic project.

(define (project z)  (apply-generic 'project z))
(define (drop z)
  (if (= (type-level z) 1) 
      z
      (let ((projected (project z)))
        (if (equ? z (raise projected))
            (drop projected)
            z))))

; Third, add type specific project procedures for each type that can project.

; in the rational package
(define (project r) 
    (make-integer (truncate (/ (numer r) (denom r)))))
(put 'project    '(rational) (lambda (x) (project x)))

; in the real package - this is a bit messy because real numbers can be either 
; integers, rational or irrational.
(define (project r) 
    (let ((exact (inexact->exact r)))
      (cond ((integer? exact)  (make-rational exact 1))
            ((rational? exact) (make-rational (numerator exact) (denominator exact)))
            (else (make-rational (truncate exact) 1)))))
(put 'project    '(real) (lambda (x) (project x)))

; in the complex package
(define (project z1)
  (make-real (real-part z1)))
(put 'project    '(complex) (lambda (x) (project x)))