sicp每日一題[2.73]

再思即可發表於2024-11-09

最近狀態不太好,再加上2.73前面的內容有點多,學的有點吃力,所以昨天就沒做。。

Exercise 2.73

Section 2.3.2 described a program that performs symbolic differentiation:

(define (deriv exp var)
  (cond ((number? exp) 0)
        ((variable? exp)
         (if (same-variable? exp var) 1 0))
        ((sum? exp)
         (make-sum (deriv (addend exp) var)
                   (deriv (augend exp) var)))
        ((product? exp)
         (make-sum (make-product
                    (multiplier exp)
                    (deriv (multiplicand exp) var))
                   (make-product
                    (deriv (multiplier exp) var)
                    (multiplicand exp))))
        ⟨more rules can be added here⟩
        (else (error "unknown expression type: DERIV" exp))))

We can regard this program as performing a dispatch on the type of the expression to be differentiated. In this situation the “type tag” of the datum is the algebraic operator symbol (such as +) and the operation being performed is deriv. We can transform this program into data-directed style by rewriting the basic derivative procedure as

(define (deriv exp var)
  (cond ((number? exp) 0)
        ((variable? exp) (if (same-variable? exp var) 1 0))
        (else ((get 'deriv (operator exp))
               (operands exp) var))))

(define (operator exp) (car exp))
(define (operands exp) (cdr exp))

a. Explain what was done above. Why can’t we assimilate the predicates number? and variable? into the data-directed dispatch?
b. Write the procedures for derivatives of sums and products, and the auxiliary code required to install them in the table used by the program above.
c. Choose any additional differentiation rule that you like, such as the one for exponents (Exercise 2.56), and install it in this data-directed system.
d. In this simple algebraic manipulator the type of an expression is the algebraic operator that binds it together. Suppose, however, we indexed the procedures in the opposite way, so that the dispatch line in deriv looked like

((get (operator exp) 'deriv) (operands exp) var)

What corresponding changes to the derivative system are required?


首先做這道題之前要先把 put 和 get 函式給加進來

(define *operation-table* (make-hash))

(define (put op-type op-name procedure)
  (hash-set! *operation-table* (list op-type op-name) procedure))

(define (get op-type op-name)
  (hash-ref *operation-table* (list op-type op-name) #f))

a. 上面的程式就是從 data-directed dispatch 表裡取出一個操作為 deriv,型別為 (operator exp) 的過程,並把表示式和變數傳給這個提取出的過程。不把 number? 和 variable? 放到 data-directed dispatch 表的原因是因為它們兩個只需要一個引數,而這個程式會給取出的過程2個引數。我看到別人的答案基本都是說這兩個過程沒有標籤,而且不需要加標籤,所以不能放入 data-directed dispatch 表,但是題目問的是 why can't,它們的解釋不是 can't 而是 needn't,不知道我的理解對不對。
b&c. 這兩問情況一樣,難度也不大,參考 2.56 的練習,只需要修改一下取運算元的邏輯就可以了,因為 “+, *, **” 本身已經作為區分採用哪個過程的符號,所以取運算元不是從第二個取,而是第一個,其他地方就照搬過來就可以了。

(define (install-deriv-package)
  ;; internal procedures
  ;; sum
  (define (make-sum a1 a2)
    (cond ((=number? a1 0) a2)
          ((=number? a2 0) a1)
          ((and (number? a1) (number? a2)) (+ a1 a2))
          (else (list '+ a1 a2))))

  (define (sum? x) (and (pair? x) (eq? (car x) '+)))

  (define (addend s) (car s))

  (define (augend s) (cadr s))

  (define (sum-deriv expr var) 
    (make-sum (deriv (addend expr) var) 
              (deriv (augend expr) var))) 

  ;; product
  (define (make-product m1 m2)
    (cond ((or (=number? m1 0) (=number? m2 0)) 0)
          ((=number? m1 1) m2)
          ((=number? m2 1) m1)
          ((and (number? m1) (number? m2)) (* m1 m2))
          (else (list '* m1 m2))))

  (define (product? x) (and (pair? x) (eq? (car x) '*)))

  (define (multiplier p) (car p))

  (define (multiplicand p) (cadr p))

  (define (product-deriv expr var) 
    (make-sum (make-product (deriv (multiplier expr) var) 
                            (multiplicand expr))
              (make-product (multiplier expr)
                            (deriv (multiplicand expr) var))))

  ;; exponentiate
  (define (make-exponentiation base exponent)
    (cond ((=number? base 0) 0)
          ((=number? base 1) 1)
          ((and (number? base) (=number? exponent 0)) 1)
          ((and (number? base) (=number? exponent 1)) base)
          ((and (number? base) (number? exponent)) (* base (make-exponentiation base (- exponent 1))))
          (else (list '** base exponent))))

  (define (base e) (car e))

  (define (exponent e) (cadr e))

  (define (exponentation-deriv expr var) 
    (make-product (exponent expr) 
                  (make-product  
                   (make-exponentiation (base expr) 
                                        (make-sum (exponent expr) -1)) 
                   (deriv (base expr) var))))
               
  ;; interface to the rest of the system
  (put 'deriv '+ sum-deriv)
  (put 'deriv '* product-deriv)
  (put 'deriv '** exponentation-deriv)
  'done)


(install-deriv-package)


(deriv '(+ x x x) 'x) 
(deriv '(* x x x) 'x) 
(deriv '(+ x (* x  (+ x (+ y 2)))) 'x) 
(deriv '(+ x (* 3 (+ x (+ y 2)))) 'x)
(deriv '(** x 3) 'x) 

; 結果如下
'done
2
'(+ x x)
'(+ 1 (+ (+ x (+ y 2)) x))
4
'(* 3 (** x 2))

d. 這樣的話,只要在使用 put 函式的時候也先傳型別,再傳操作就可以了。

相關文章