SICP 読み (88) 2.5.3 例: 記号代数

問題 2.92 はスルー。あと 5 問で 2 章が終わるんですが、残りの 3 頁が見るからに重たそう。ってか一番重いの 2.92 なんだろうな。

問題 2.93

まず、問題 2.88 を流用して有理数なソレを盛り込む。現時点で include はしてるんですが、仕様通りの実装にはなっていないハズ。

(define (install-rational-package)
 ;; internal procedures
 (define (numer x) (car x))
 (define (denom x) (cdr x))
 (define (make-rat n d)
   (let ((g (gcd n d)))
     (cons (/ n g) (/ d g))))
 (define (add-rat x y)
   (make-rat (+ (* (numer x) (denom y))
                (* (numer y) (denom x)))
             (* (denom x) (denom y))))
 (define (sub-rat x y)
   (make-rat (- (* (numer x) (denom y))
                (* (numer y) (denom x)))
             (* (denom x) (denom y))))
 (define (mul-rat x y)
   (make-rat (* (numer x) (numer y))
             (* (denom x) (denom y))))
 (define (div-rat x y)
   (make-rat (* (numer x) (denom y))
             (* (denom x) (numer y))))
 ;; interface to rest of the system
 (define (tag x) (attach-tag 'rational x))
 (put 'add '(rational rational)
      (lambda (x y) (tag (add-rat x y))))
 (put 'sub '(rational rational)
      (lambda (x y) (tag (sub-rat x y))))
 (put 'mul '(rational rational)
      (lambda (x y) (tag (mul-rat x y))))
 (put 'div '(rational rational)
      (lambda (x y) (tag (div-rat x y))))

 (put 'make 'rational
      (lambda (n d) (tag (make-rat n d))))
 'done)
(define (make-rational n d)
 ((get 'make 'rational) n d))

make-rat を変更し、とあるな。単純に cons するだけにしておけば動く??

上記実装 (make-rat は変更) を以下の試験で確認。

 ("include rational package"
  (setup (lambda ()
           (install-rational-package)
           (install-scheme-number-package)
           (install-polynomial-package)))
  ("sample"
   (let ((p1 (make-polynomial 'x '((2 1) (0 1))))
         (p2 (make-polynomial 'x '((3 1) (0 1)))))
     (let ((rf (make-rational p2 p1)))
       (assert-equal 'rational (car rf))
       (assert-equal p2 (cadr rf))
       (assert-equal p1 (cddr rf))
       ))
   )
  )

一応動いている。次は (add rf rf) で約分されていない事を確認か。assert を追加。

 ("include rational package"
  (setup (lambda ()
           (install-rational-package)
           (install-scheme-number-package)
           (install-polynomial-package)))
  ("sample"
   (let ((p1 (make-polynomial 'x '((2 1) (0 1))))
         (p2 (make-polynomial 'x '((3 1) (0 1)))))
     (let ((rf (make-rational p2 p1)))
       (assert-equal 'rational (car rf))
       (assert-equal p2 (cadr rf))
       (assert-equal p1 (cddr rf))
       (let ((test (add rf rf)))
         (assert-equal 'rational (car test))
         (assert-equal p1 (cddr test))
         (assert-equal (add p2 p2) (cadr test))
         )
       ))
   )
  )

試験してみると NG との事。

-- (test case) include rational package: E
./test/test-2.5.3.scm:20: (add rf rf)
Error occurred in sample
*** ERROR: operation * is not defined between (polynomial x (3 1) (0 1)) and (polynomial x (2 1) (0 1))
./test/test-2.5.3.scm:20: (add rf rf)

演算子が + とか * とかになってるからか。- もあるが neg して add すれば OK ですな。上記の試験がビンゴなのかどうか、は実装を修正しないと分からん。(を

で、試験してみたんですが、上記の試験では NG。_分数を最低項まで引き下げ_んというのはこれですか。本来であれば

\frac{2*(x^3+1)*(x^2+1)}{(x^2+1)*(x^2+1)} = \frac{2x^5+2x^3+2x^2+2}{x^4+2x^2+1}
ではなくて
\frac{2*(x^3+1)*(x^2+1)}{(x^2+1)*(x^2+1)} = \frac{2*(x^3+1)}{(x^2+1)}
にならないとイケないんだな。でも今の実装ではそれは無理。(add rf rf) です。

修正した試験が以下。

 ("include rational package"
  (setup (lambda ()
           (install-rational-package)
           (install-scheme-number-package)
           (install-polynomial-package)))
  ("sample"
   (let ((p1 (make-polynomial 'x '((2 1) (0 1))))
         (p2 (make-polynomial 'x '((3 1) (0 1)))))
     (let ((rf (make-rational p2 p1)))
       (assert-equal 'rational (car rf))
       (assert-equal p2 (cadr rf))
       (assert-equal p1 (cddr rf))
       (let ((test (add rf rf)))
         (assert-equal 'rational (car test))
         (assert-equal (make-polynomial 'x '((4 1) (2 2) (0 1)))
                       (cddr test))
         (assert-equal (make-polynomial 'x '((5 2) (3 2) (2 2) (0 2)))
                       (cadr test))
         )
       ))
   )
  )

あと、修正した rational-package が以下。

(define (install-rational-package)
 ;; internal procedures
 (define (numer x) (car x))
 (define (denom x) (cdr x))
 (define (make-rat n d)
;    (let ((g (gcd n d)))
;      (cons (/ n g) (/ d g))))
   (cons n d))
 (define (add-rat x y)
   (make-rat (add (mul (numer x) (denom y))
                  (mul (numer y) (denom x)))
             (mul (denom x) (denom y))))
 (define (sub-rat x y)
   (make-rat (add (mul (numer x) (denom y))
                  (neg (mul (numer y) (denom x))))
             (mul (denom x) (denom y))))
 (define (mul-rat x y)
   (make-rat (mul (numer x) (numer y))
             (mul (denom x) (denom y))))
 (define (div-rat x y)
   (make-rat (mul (numer x) (denom y))
             (mul (denom x) (numer y))))
 ;; interface to rest of the system
 (define (tag x) (attach-tag 'rational x))
 (put 'add '(rational rational)
      (lambda (x y) (tag (add-rat x y))))
 (put 'sub '(rational rational)
      (lambda (x y) (tag (sub-rat x y))))
 (put 'mul '(rational rational)
      (lambda (x y) (tag (mul-rat x y))))
 (put 'div '(rational rational)
      (lambda (x y) (tag (div-rat x y))))

 (put 'make 'rational
      (lambda (n d) (tag (make-rat n d))))
 'done)
(define (make-rational n d)
 ((get 'make 'rational) n d))

一応、手を入れた手続きについては試験をしておいた方が良いな。

追記

試験を作成。微妙だったのは scheme-number なパケジに neg 演算が無かったコト。最初、rational なパケジに neg を定義してしまい、微妙にハマる。

手を入れた scheme-number-package を以下に。

(define (install-scheme-number-package)
 (put 'add '(scheme-number scheme-number) +)

 (put 'sub '(scheme-number scheme-number) -)

 (put 'mul '(scheme-number scheme-number) *)

 (put 'div '(scheme-number scheme-number) /)

 (put 'neg '(scheme-number) -)

 (put '=zero? '(scheme-number) zero?)

 (put 'make 'scheme-number
      (lambda (x) x))
 'done)

(define (make-scheme-number n)
 ((get 'make 'scheme-number) n))

で、演算子の試験が以下。簡単に済ませスギかも。

  ("rational package (add)"
   (let ((r1 (make-rational 1 2))
	 (r2 (make-rational 1 3)))
     (let ((result (add r1 r2)))
       (assert-equal 'rational (car result))
       (assert-equal 5 (cadr result))
       (assert-equal 6 (cddr result))))
   )

  ("rational package (sub)"
   (let ((r1 (make-rational 1 2))
	 (r2 (make-rational 1 3)))
     (let ((result (sub r1 r2)))
       (assert-equal 'rational (car result))
       (assert-equal 1 (cadr result))
       (assert-equal 6 (cddr result))))
   )

  ("rational package (mul)"
   (let ((r1 (make-rational 1 2))
	 (r2 (make-rational 1 3)))
     (let ((result (mul r1 r2)))
       (assert-equal 'rational (car result))
       (assert-equal 1 (cadr result))
       (assert-equal 6 (cddr result))))
   )

  ("rational package (div)"
   (let ((r1 (make-rational 1 2))
	 (r2 (make-rational 1 3)))
     (let ((result (div r1 r2)))
       (assert-equal 'rational (car result))
       (assert-equal 3 (cadr result))
       (assert-equal 2 (cddr result))))
   )

一応パスしてると見て、remainder-terms の検討に着手か。2.91 の流用という部分で言えば、2.94 はさほどハードルは高くないな。がしかし、それ以降が微妙。