2.73
练习 2.73 2.3.2节描述了一个执行符号求导的程序:
(define (deriv exp var)
(cond ((number? exp) 0)
((variable? exp) (if (same-varialbe? 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))))
<更多规则可以加在这里>
(else (error "unknown expression type -- DERIV" exp))))
可以认为,这个程序是在执行一种基于被求导表达式类型的分派工作。在这里,数据的“类型标志”就是代数运算符(例如+),需要执行的操作是deriv。我们也可以将这一程序变换到数据导向的风格,将基本求导过程重新写成:
(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)请解释上面究竟做了些什么。为什么我们无法将相近的谓词number?和same-variable?也加入数据导向分派中?
b)请写出针对和式与积式的求导过程,并把它们安装到表格里,以便上面程序使用所需要的辅助性代码。
c)请选择一些你希望包括的求导规则,例如对乘幂(练习2.56)求导等等,并将它们安装到这一数据导向的系统里。
d)在这一简单的代数运算中,表达式的类型就是构造起它们来的代数运算符。假定我们想以另一种相反的方式做索引,使得deriv里完成分派的代码行像下面这样:
((get (operator exp) 'deriv) (operands exp) var)
求导系统里还需要做哪些相应的改动?
a) 上面将对不同类型的导数求法放在一张表里,通过查表法来进行计算。大致建立了如下形式的表:
sum | product | |
---|---|---|
deriv | deriv-sum | deriv-product |
number? 和 same-variable? 不能使用 (car expression) 对应到表中的类型,故不能放在表中。
b)
先复用一些已有函数:
(define variable? symbol?)
(define (same-variable? v1 v2)
(and (variable? v1) (variable? v2) (eq? v1 v2))
)
(define (get op type)
(cond
((and (eq? op 'deriv) (eq? type '+)) 'deriv-sum)
(else 'deriv-product)
)
)
(define (=number? v n)
(if (number? v)
(= v n)
#f
)
)
(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 addend cadr)
(define augend caddr)
以下是针对和式求导:
(define (deriv-sum exp var)
(make-sum
(deriv (addend exp) var)
(deriv (augend exp) var)
)
)
(deriv-sum '(+ x y) 'x)
以下是针对积式求导:
(define (deriv-product exp var)
(make-sum
(make-product
(multiplier exp)
(deriv (multiplicand exp) var)
)
(make-product
(deriv (multiplier exp) var)
(multiplicand exp)
)
)
)
(define multiplier cadr)
(define multiplicand caddr)
(define (make-product m1 m2)
(cond
((=number? m1 1) m2)
((=number? m2 1) m1)
((=number? m1 0) 0)
((=number? m2 0) 0)
((and (number? m1) (number? m2)) (* m1 m2))
(else (list m1 '* m2))
)
)
(deriv-product '(* x x) 'x)
c) 将表格扩充一下:
sum | product | exp | |
---|---|---|---|
deriv | deriv-sum | deriv-product | deriv-exp |
然后查表函数也需要相应地更新一下:
(define (get op type)
(cond
((and (eq? op 'deriv) (eq? type '+)) deriv-sum)
((and (eq? op 'deriv) (eq? type '*)) deriv-product)
(else deriv-exp)
)
)
(define (deriv-exp expr var)
(cond
((= (exponent expr) 0) 0)
((= (exponent expr) 1) 1)
(else
(make-product
(exponent expr)
(make-exponentiation
(base expr)
(- (exponent expr) 1)
)
)
)
)
)
(define base cadr)
(define exponent caddr)
(define (make-exponentiation m1 m2)
(list '** m1 m2)
)
; 期待得到 3x^2 的结果
(deriv-exp '(** x 3) 'x)
以上分别调用了 deriv-sum, deriv-product 和 deriv-exp,试一下直接调用 deriv,需要更新一下对 exp 的处理:
(define addend car)
(define augend cadr)
(deriv '(+ x 1) 'x)
d) 相反的方式索引,只是将表格换成了像下面这样:
deriv | |
---|---|
sum | deriv-sum |
product | deriv-product |
exp | deriv-exp |
求导程序不需要做改动。