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 same-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?
解释
(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))
这一段代码已经完成了数据导向的设计的主要框架。举例来说,当我们调用 (deriv '(+ x 3) 'x)
的时候,现在的 direv 会:
1. 从 '(+ x 3)
的式子中取出 operator,即 '+
。 2. 在二维表中查找以 derv 为横坐标,+ 为纵坐标位置的过程 3. 把 operands 和 var,即分别为 (x 3) 与 x ,传递到上面找到的过程中。
我们无法把 number? 与 variable? 这个东西放到表中。这是因为传递到表中的,是 (operator exp),提取到的过程又将去处理 (operands exp)。传递的这些参数对 number? variable? same-variable? 都是无用的,所以不能放到表中。
sum 和 product 的求导过程
我们将分别在过程 install-sum-package 与 install-product-package 两个过程里面写相应的过程并安装到表上。
在 install-sum-package 中,我们主要写对 sum 求导数的过程 deriv-sum,这里 deriv-sum 得到的参数将与之前系统的不一样–这里的参数已经是经过 deriv 过程剥离 operator 与 operands 后只传递进来的 operands。也就是说,在这个 package 里面,只需要对两个 operands 处理就可以。
另外一点需要注意的是内部的数据结构的表示。我们有多种选择,把 (+ x 3) 的两个参数 x 和 3 在 package 内部组织为 (list x 3) 或者 (cons x 3)。但当用户输入 (+ x 3) 时,deriv 会将 (operands '(+ x 3))
,即 (list x 3) 传递进来,所以我们就没有办法选用 (cons x 3) 这种的内部表示形式了。
最后一点要注意的,是将过程安装到表的相应位置。在下面的代码中,各个过程将被安装到以下位置
============ =========== ===============
table '+ '*
------------ ----------- ---------------
'deriv deriv-sum deriv-product
'constructor make-sum make-product
============ =========== ===============
需要安装 make-sum 的原因是在 deriv-product 的时候需要用到 make-sum 这一过程。
(define (install-sum-package)
(define (make-sum a1 a2)
(cond ((=number? a1 0) a2)
((=number? a2 0) a1)
((and (number? a1) (number? a2)) (+ a1 a2))
(else (tag a1 a2))))
(define (addend s) (car s))
(define (augend s) (cadr s))
(define (deriv-sum exp var)
(make-sum (deriv (addend exp) var)
(deriv (augend exp) var)))
;;put into table
(define (tag s1 s2) (tag-operator sum-symbol s1 s2))
(put 'deriv sum-symbol deriv-sum)
(put 'constructor sum-symbol make-sum))
;; product
(define (install-product-package)
(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 (tag m1 m2))))
(define (multiplier p) (car p))
(define (multiplicand p)
(cadr p))
(define (deriv-product exp var)
((get 'constructor sum-symbol)
(make-product (multiplier exp)
(deriv (multiplicand exp) var))
(make-product (deriv (multiplier exp) var)
(multiplicand exp))))
;; rest of the package
(define (tag m1 m2) (tag-operator product-symbol m1 m2))
(put 'deriv product-symbol deriv-product)
(put 'constructor product-symbol make-product))
修改索引方式
如果需要修改索引方式,那么所有的 install-package 中 put 的参数都需要进行变更。当然,我们也可以修改 put 过程。