views:

508

answers:

1

So here's one way to define a sum as a message passing object:

    (define (make-sum . exps)
  (let ((len (length exps)))
    ;; first handle variable length operands
    (cond 
     ((= len 0) (make-number 0)) ;; base case for addition
     ((= len 1) (car exps))      ;; might as well drop the sum if only 1 argument
     ((> len 2) 
       (make-sum (car exps) (apply make-sum (cdr exps))))
       ;; apply is described in section 2.4, footnote 47
       ;; > 2 operands: break up into 2 for simplicity in handling
     (else  ;; must be exactly 2 operands
      (let ((op1 (car exps))
            (op2 (cadr exps)))
        (cond ((op1 'zero?) op2) ;; exp + 0 --> exp
              ((op2 'zero?) op1)
              ((and (op1 'number?) (op2 'number?))
               (make-number (+ (op1 'value) (op2 'value)))) ;; num + num --> num
              (else ;; create a new message-passing object representing the sum
               (lambda (m . args)
                 (cond ((eq? m 'derive) 
                        (if (and (= (length args) 1)
                                 (symbol? (car args)))
                            ;; derivative of a sum is the sum of the derivatives
                            ;; of the parts of the sum
                            (make-sum (op1 'derive (car args)) 
                                      (op2 'derive (car args)))
                            (error "derive needs a variable argument")))
                       ((eq? m 'print) (list '+ (op1 'print) (op2 'print)))
                       ((eq? m 'zero?) #f)
                       ((eq? m 'number?) #f)
                       ((eq? m 'value) 
                        (error "should not be asking for the value of a sum expression"))
                       ((eq? m 'evaluate) 
                        (if (and (= (length args) 2)
                                 (symbol? (car args))
                                 (number? (cadr args)))
                            (let ((eop1 (op1 'evaluate (car args) (cadr args)))
                                  (eop2 (op2 'evaluate (car args) (cadr args))))
                              (make-sum eop1 eop2))
                            (error "evaluate needs a variable symbol and a number")))
                       ((eq? m 'has-constant-multiplier?) #f)
                       ((eq? m 'merge-constant) 
                        (error "should not be merging a constant with an addition"))
                       (else (error "unknown message" m)))))))))))

But this can be rewritten as:

(define (make-sum exp1 exp2)
  (cond ((exp1 'zero?) exp2) ;; exp + 0 --> exp
        ((exp2 'zero?) exp1)
        ((and (exp1 'number?) (exp2 'number?))
         (make-number (+ (exp1 'value) (exp2 'value)))) ;; num + num --> num
        (else  ;; create a new message-passing object representing the sum
         (lambda (m . args)
           (cond ((eq? m 'derive) 
                  (if (and (= (length args) 1)
                           (symbol? (car args)))
                      (let ((variable (car args)))
                        ;; derivative of a sum is the sum of the derivatives
                        ;; of the parts of the sum
                        (make-sum (exp1 'derive variable) 
                                  (exp2 'derive variable)))
                      (error "derive needs a variable argument")))
                 ((eq? m 'print) (list '+ (exp1 'print) (exp2 'print)))
                 ((eq? m 'zero?) #f)
                 ((eq? m 'number?) #f)
                 ((eq? m 'value) 
                  (error "should not be asking for the value of a sum expression"))
                 ((eq? m 'evaluate) 
                  (if (and (= (length args) 2)
                           (symbol? (car args))
                           (number? (cadr args)))
                      (let ((variable (car args))
                            (number   (cadr args)))
                        (let ((exp1-eval (exp1 'evaluate variable number))
                              (exp2-eval (exp2 'evaluate variable number)))
                          (make-sum exp1-eval exp2-eval)))
                      (error "evaluate needs a variable symbol and a number")))
                 (else (error "unknown message: " m)))))))

How would I change the function I wrote for defining a product as a message-passing object into the second format above? Here's the code I wrote:

(define (make-product . exps)
  (let ((len (length exps)))
    (cond
     ((= len 0) (make-number 1)) ;; base case for multiplication
     ((= len 1) (car exps))      ;; might as well drop the product if only 1 argument
     ((> len 2) (make-product (car exps) (apply make-product (cdr exps))))
     (else
      (let ((op1 (car exps))
            (op2 (cadr exps)))
        (cond ((op1 'zero?) (make-number 0))
              ((op2 'zero?) (make-number 0))
              ((and (op1 'number?) (= (op1 'value) 1)) op2) ;; multiplicative identity
              ((and (op2 'number?) (= (op2 'value) 1)) op1) ;; multiplicative identity
              ((and (op1 'number?) (op2 'has-constant-multiplier?))
               (op2 'merge-constant (op1 'value)))
              ((and (op2 'number?) (op1 'has-constant-multiplier?))
               (op1 'merge-constant (op2 'value)))
              (else
               (lambda (m . args)
                 (cond ((eq? m 'derive) 
                        (if (and (= (length args) 1)
                                 (symbol? (car args)))
                            (make-sum 
                             (make-product (op1 'derive (car args)) op2)
                             (make-product op1 (op2 'derive (car args))))
                            (error "derive needs a variable argument")))
                       ((eq? m 'print) (list '* (op1 'print) (op2 'print)))
                       ((eq? m 'zero?) #f)
                       ((eq? m 'number?) #f)
                       ((eq? m 'value) 
                        (error "should not be asking for the value of an product expression"))
                       ((eq? m 'evaluate) 
                        (if (and (= (length args) 2)
                                 (symbol? (car args))
                                 (number? (cadr args)))
                            (let ((eop1 (op1 'evaluate (car args) (cadr args)))
                                  (eop2 (op2 'evaluate (car args) (cadr args))))
                              (make-product eop1 eop2))
                            (error "evaluate needs a variable symbol and a number")))
                       ((eq? m 'has-constant-multiplier?) 
                        (or (op1 'has-constant-multiplier?)
                            (op2 'has-constant-multiplier?)))
                       ((eq? m 'merge-constant) 
                        (if (and (= (length args) 1)
                                 (number? (car args)))
                            (cond ((op1 'has-constant-multiplier?)
                                   (make-product 
                                    (op1 'merge-constant (car args))
                                    op2))
                                  ((op2 'has-constant-multiplier?)
                                   (make-product 
                                    op1
                                    (op2 'merge-constant (car args))))
                                  (else 
                                   (error "should not be calling merge-constant on a product with no constant multiplier")))
                            (error "invalid arguments to merge-constant")))
                       (else (error "unknown message" m)))))))))))
A: 

The main differences between the two make-sum functions is the second is a binary (2-argument) function while the first has variable arity. Remove the first cond from the first make-sum, and you basically have the second make-sum. The second doesn't handle 'has-constant-multiplier? or 'merge-constant, but the other differences are entirely cosmetic. From that, you can conclude that stripping the vararg cases from make-product is what you need to do.

outis