SICPを読む(85) 問題 2.73 記号微分をデータ主導に

やっと問題に入ります。

問題 2.73

前に作った記号微分プログラムをデータ主導に書き直す問題

a

(deriv '(+ 2 x) 'x)ならば、'+と'derivにマッチする、手続きを検索し、見付かった手続きに、引数(2 x)と,'xを作用させる。

この微分プログラムでは(+ ...)というのをデータとして扱っている為、2,xは振り分けに吸収できない。

仮に、2,xもデータ主導としてに振り分けたい場合は、引数に自分自身を持つメソッドとして登録すればいいんじゃないかなぁと。

(if (pair? exp)
    ;(+ ...)の処理
    ((get 'derv (get-type exp)) exp val))

数字を微分。記号を微分。みたいなかんじ。

b,c,d

一気にやります。

(define (install-deriv-package)
  ;; private
  (define (=number? exp num)
    (and (number? exp) (= exp num)))

  (define addend car)
  (define augend cadr)
  (define multiplier car)
  (define multiplicand cadr)
  (define base car)
  (define exponent cadr)

  (define (make-sum ex1 ex2)
    (cond ((=number? ex1 0) ex2)
          ((=number? ex2 0) ex1)
          ((and (number? ex1) (number? ex2)) (+ ex1 ex2))
          (else
            (list '+ ex1 ex2))))

  (define (make-product ex1 ex2)
    (cond ((or (=number? ex1 0) (=number? ex2 0)) 0)
          ((=number? ex1 1) ex2)
          ((=number? ex2 1) ex1)
          ((and (number? ex1) (number? ex2)) (* ex1 ex2))
          (else (list '* ex1 ex2))))
  (define (make-exponentiation ex1 ex2)
    (cond ((=number? ex2 0) 1)
          ((=number? ex2 1) ex1)
          ((number? ex1) (number? ex2) (expt ex1 ex2))
          (else (list '** ex1 ex2))))

  ;; public
  (define (sum exp var)
    (make-sum (deriv (addend exp) var)
              (deriv (augend exp) var)))
  (define (product exp var)
    (make-sum (make-product (multiplier exp)
                            (deriv (multiplicand exp) var))
              (make-product (deriv (multiplier exp) var)
                            (multiplicand exp))))
  (define (exponentiation exp var)
    (make-product
      (make-product (exponent exp)
                    (make-exponentiation (base exp)
                                         (make-sum (exponent exp) -1)))
      (deriv (base exp) var)))
  (put '+ 'deriv sum)
  (put '* 'deriv product)
  (put '** 'deriv exponentiation))

(install-deriv-package)

あんまりデータ主導になってないかも。+パッケージ、*パッケージ、**パッケージに分けた方が良い回答だと思う。新たな規則を追加したときに元のパッケージは変更しなくてもいい。

これだと、公開、非公開を作っただけであまりデータ主導としての役割が感じられない。

やっぱり何か違うな。練り直し!!

練り直し版

データ主導なんだから、こうだよね。

(define (make operator ex1 ex2)
  ((get 'make operator) ex1 ex2))

(define (install-sum-package)
  ;; private
  (define addend car)
  (define augend cadr)

  ;; public
  (define (make-sum ex1 ex2)
    (cond ((=number? ex1 0) ex2)
          ((=number? ex2 0) ex1)
          ((and (number? ex1) (number? ex2)) (+ ex1 ex2))
          (else
            (list '+ ex1 ex2))))

  (define (deriv-sum exp var)
    (make '+
          (deriv (addend exp) var)
          (deriv (augend exp) var)))

  (put 'deriv '+ deriv-sum)
  (put 'make  '+ make-sum))

(install-sum-package)


(define (install-product-package)
  ;; private
  (define multiplier car)
  (define multiplicand cadr)

  ;; public
  (define (make-product ex1 ex2)
    (cond ((or (=number? ex1 0) (=number? ex2 0)) 0)
          ((=number? ex1 1) ex2)
          ((=number? ex2 1) ex1)
          ((and (number? ex1) (number? ex2)) (* ex1 ex2))
          (else (list '* ex1 ex2))))

  (define (deriv-product exp var)
    (make '+
          (make '*
                (multiplier exp)
                (deriv (multiplicand exp) var))
          (make '*
                (deriv (multiplier exp) var)
                (multiplicand exp))))

  (put 'deriv '* deriv-product)
  (put 'make  '* make-product))

(install-product-package)

メソッドがmakeで統一出来ました。これこそデータ主導の目的っすね。(define (make ...))はインターフェイスといった感じでしょうか。

これなら、微分パッケージではないので、普通に計算するメソッドを追加しても問題ありません。


それにしても、オブジェクトが「たす」とか「かける」とか・・・普通じゃないオブジェクトを扱ってるのがSICPの変態さ。「たす」とか「かける」を第一級のオブジェクトとして扱えるというのがSchemeの凄い所だと思います。


「たす」オブジェクトが「微分」するメソッドを持ってるか・・・聞いたことねぇYO!!