問題2-57〜58
微分記号に関する問題の続きです。前回のエントリーの最後で、複数の要素を同じ括弧内で足し合わせられるようにするためには、ドット末尾記法を使えば良いんじゃないかと書きましたが、これは大きな勘違いでした。つまり、make-sumをする時に、a2が一つか複数かということを場合分けしなくてはならないのではと思っていたのですが、make-sumが呼び出されるのは、deriv手続きの中であって、ここの手続きは変えないという縛りがあるのだから、make-sumの引数構造自体は変える必要がないということに気づきました。重要なのは、make-sumによって生成されたデータから、値を引き出す時です。
というわけで、和のデータ構造から、augentを引き出す時の手続きを以下のように書きました。
(define (augend s) (let ((rest (caddr s))) (if (pair? rest) (make-sum (car rest) (cdr rest)) rest)))
もし、二つ以上の要素が存在していたら、二番目の要素とそれ以降の要素の「和」を返すという手続きです。これでよさそうだと思って、「積」の方も実装したら、エラーが発生してしまいました。再帰的に呼び出されているderiv手続きの中で、multiplicandが呼び出されているのが問題のようです。そもそも、上記のaugendが動いたのは、derivの中で呼び出されているからだということに気づきました。三つ目以降の要素が、正しい和積のデータ構造になっておらず、ただのリストになってしまっています。
これを改善するためには、三つ目以降の要素についても、正しいデータ構造を作ってやる必要があります。tostudycsさんの勉強メモを見てしまいましたが、以下のように表現すれば、それが実現することができます。
(define (augend s) (define (iter items) (if (null? items) 0 (make-sum (car items) (iter (cdr items))))) (iter (cddr s))) (define (multiplicand p) (define (iter items) (if (null? items) 1 (make-product (car items) (iter (cdr items))))) (iter (cddr p))) (deriv '(+ x 3 y) 'x) ;=>1 (deriv '(* x y x) 'x) ;=>(+ (* x y) (* y x))
二つ目の式についても、まとまりきれていませんが、正しい答えが返ってきているようです。
次の問題2-58は、和や積の作り方、判定、要素の取り出し方を以下のように修正しました。
(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? (cadr x) '+))) (define (addend s) (car s)) (define (augend s) (caddr s)) (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? (cadr x) '*))) (define (multiplier p) (car p)) (define (multiplicand p) (caddr p)) (define (make-exponentiation b e) (cond ((= e 0) 1) ((= e 1) b) (else (list b '** e)))) (define (exponentiation? ex) (and (pair? ex) (eq? (cadr ex) '**))) (define (base ex) (car ex)) (define (exponent ex) (caddr ex)) (deriv '(x + 3) 'x) ;=>1 (deriv '(x * y) 'x) ;=>y (deriv '((x * y) * (x + 3)) 'x) ;=>((x * y) + (y * (x + 3))) (deriv '(3 * (x ** 3)) 'x) ;=>(3 * (3 * (x ** 2)))
よく見慣れた式が返ってきたので、ちょっと感動してしまいました。上記は要素二つという制約のもとでの手続きですが、複数の要素、複数の演算子を許容できるよう改善できるか、については頭が回らなかったため、機会があれば取り組んでみたいと思います。