SICP 問題 2.73(記号微分のデータ主導化)

問題

2.3.2節で記号微分を行うプログラムを述べた。

(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))))
	<更に多くの規則をここに追加できる>
	(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? や variable? がデータ主導の振り分けに吸収できないのはなぜか。

b. 和と積の微分の手続きを書き、上のプログラムで使う表に、それらを設定するのに必要な補助プログラムを書け。

c. (問題2.56)のべき乗のような、その他の微分規則を選び、このデータ主導システムに設定せよ。

d. この代数式操作では、式の型はそれを結合している代数演算である。しかし手続きの目印を反対にし、deriv の振り分けを

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

のようにしたとしよう。微分システムには対応したどのような変更が必要か。


解答

おお、早速データ主導型システムの例題だな。パッケージ化すりゃいいのかな。

a.

何をやっているかについて。
「数字」と「変数」以外の「型タグ(演算子)が付加されている式オブジェクト」に対して適用させる汎用微分手続きを、その型タグを使用して取得し、残りの引数を取得した実装レベルの微分手続きで微分するようにしている。


述語 number? や variable? がデータ主導の振り分けに吸収できない理由について。
「数値」と「変数」もオブジェクトに相当するが、これらは単項の型タグを持たない式オブジェクトである為、テーブルに実装レベルの微分手続きを割り当てることができない。なので、別途 cond の条件ロジックに定義しておく必要がある。

b.

和と積の微分の手続きを書き、上のプログラムで使う表に、それらを設定するのに必要な補助プログラムを書け。
「型タグ」に相当するのは「+」「*」だよなぁ。これが一つ目の軸になって、それらにまつわる微分演算がもう一つの軸になる感じ?

;積の構成子から利用される、和の構成子を使う為の汎用手続き?
(define (make-exp operator exp1 exp2)
  ((get 'make-exp operator) exp1 exp2))

;和の式オブジェクトに関するパッケージ
(define (install-sum-deriv-package)
  ;内部手続き
  ;構成子
  (define (make-sum exp1 exp2)
    (cond ((=number? exp1 0) exp2)
          ((=number? exp2 0) exp1)
          ((and (number? exp1) (number? exp2)) (+ exp1 exp2))
          (else
            (list '+ exp1 exp2))))
  ;選択子
  (define (addend exp) (cadr exp))
  (define (augend exp) (cddr exp))

  ;システムの他の部分とのインタフェース
  (put 'make-exp '+ make-sum)
  (put 'deriv '+ 
       (lambda (exp var)
	 (make-exp '+
		   (deriv (addend exp) var)
		   (deriv (augend exp) var))))
  'done)

;パッケージの手続きを評価し、有効にする。
(install-sum-deriv-package)

;積の式オブジェクトに関するパッケージ
(define (install-product-deriv-package)
  ;内部手続き
  ;構成子
  (define (make-product exp1 exp2)
    (cond ((or (=number? exp1 0) (=number? exp2 0)) 0)
          ((=number? exp1 1) exp2)
          ((=number? exp2 1) exp1)
          ((and (number? exp1) (number? exp2)) (* exp1 exp2))
          (else (list '* exp1 exp2))))
  ;選択子
  (define (multiplier exp) (cadr exp))
  (define (multiplicand exp) (cddr exp))
  ;微分
  (define (deriv exp var)
    (make-exp '+
	      (make-exp '*
			(multiplier exp)
			(deriv (multiplicand exp) var))
	      (make-exp '*
			(deriv (multiplier exp) var)
			(multiplicand exp))))

  ;システムの他の部分とのインタフェース
  (put 'make-exp '* make-product)
  (put 'deriv '* deriv)
  'done)

;パッケージの手続きを評価し、有効にする
(install-product-deriv-package)

ちょっと構成子が微妙。これであってるのか?


c.

べき乗用のパッケージを作る感じ。

;べき乗の式オブジェクトに関するパッケージ
(define (install-exponentiation-deriv-package)
  ;内部手続き
  ;構成子
  (define (make-exponentiation base exponent)
    (cond ((=number? exponent 0) 1)
	  ((=number? exponent 1) base)
	  (else (list '** base exponent))))
  ;選択子
  (define (base exp) (cadr exp))
  (define (exponent exp) (caddr exp))
  ;微分
  (define (deriv exp var)
    (let ((n (exponent exp)))
      (cond ((= n 0) 1)
	    ((= n 1) n)
	    (else
	     (make-exp '*
		       n
		       (make-exponentiation (base exp)
					    (- n 1)))))))
  ;システムの他の部分とのインタフェース
  (put 'make-exp '** make-exponentiation)
  (put 'deriv '** deriv)
  'done)

(install-exponentiation-deriv-package)
d.

put の第1引数と第2引数を入れ替えればいい感じ?

この問題、もう少しちゃんと考え直した方がいいような気がするがなぁ〜。