SICP 読み (62) 2.5.1 汎用算術演算

週末はばたばたしてて、何もできず。次の 2.5.2 節はハードル高そうなんですがマルチでできるのかどうなのか。

問題 2.80

2.79 よりはハードルが低そうなんですが、やる前に同じ風なコト言ってるしなぁ。

とりあえず scheme-number から。試験はこれで OK か。

 ("=zero? test"
  (let ((t1 (make-scheme-number 0))
        (t2 (make-scheme-number 1)))
    (assert-true (=zero? t1))
    (assert-false (=zero? t2)))
  )

あ、これは実装簡単だな。(今気づいた

(define (install-scheme-number-package)
-- 中略 --
 (put '=zero? 'scheme-number zero?)
 'done)

で、試験してみたら失敗。トップレベルで手続きを作ってない。

(define (=zero? x) (apply-generic '=zero? x))

再度試験も失敗。メセジ確認すると

./test/test-2.5.2.scm:56: (assert-true (=zero? t1))
Error occurred in =zero? test
*** ERROR: No method for these types -- APPLY-GENERIC (=zero? (scheme-number))
./test/test-2.5.2.scm:56: (assert-true (=zero? t1))

(一部のみ抜粋)
との事。

apply-generic で get '=zero? (scheme-number)
になるんだよ。日を空けると忘れるなぁ。とりあえず、以下のように修正したら試験は通りました。

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

次は有理数ですが、試験は以下か。

 ("=zero? test"
  (let ((t1 (make-rational 0 0))
        (t2 (make-rational 1 1)))
    (assert-true (=zero? t1))
    (assert-false (=zero? t2)))
  )

すげぇいい加減感満点ですがとりあえず仕様確認はこれでできる。実装は

(define (install-rational-package)
 ;; internal procedures
-- 中略 --
 (define (=zero-rat? x)
  (and (zero? (numer x)) (zero? (denom x))))
 ;; interface to rest of the system
-- 中略 --
 (put '=zero? '(rational) =zero-rat?)
 'done)

みたいな感じで試験。通らん。やっぱ easy に考えスギですな。アセってやるとロクな事ないぞ、っていう好例かと。
見てみると最初の assert が #f になっている。確認してみると

gosh> (make-rational 0 0)
(rational #<nan> . #<nan>)
gosh>

なんじゃこれは、という事で make-rational 見てみると以下。

 (define (make-rat n d)
 (let ((g (gcd n d)))
   (cons (/ n g) (/ d g))))

わははは。0 除算が通るのか。それは良いとしてどう取り扱えば良いかなぁ。

  • n か d が 0 なら両方ゼロ
  • g が 0 なら両方ゼロ

有理数として分子と分母な管理をしている、という事を踏まえて二番目案を採用。

 (define (make-rat n d)
 (let ((g (gcd n d)))
   (if (zero? g)
       (cons 0 0)
       (cons (/ n g) (/ d g)))))

微妙なオトシ穴が結構あります。てか、ヤッツケ感満点だし。

complex は少しきちんと実装を検討した方が良いな。とりあえず試験は以下で。

 ("=zero? test (rectangular)"
  (let ((t1 (make-complex-from-real-imag 0 0))
        (t2 (make-complex-from-real-imag 0 1))
        (t3 (make-complex-from-real-imag 1 0))
        (t4 (make-complex-from-real-imag 1 1)))
    (assert-true (=zero? t1))
    (assert-false (=zero? t2))
    (assert-false (=zero? t3))
    (assert-false (=zero? t4)))
  )

 ("=zero? test (polar)"
  (let ((t1 (make-complex-from-mag-ang 0 0))
        (t2 (make-complex-from-mag-ang 0 1))
        (t3 (make-complex-from-mag-ang 1 0))
        (t4 (make-complex-from-mag-ang 1 1)))
    (assert-true (=zero? t1))
    (assert-false (=zero? t2))
    (assert-false (=zero? t3))
    (assert-false (=zero? t4)))
  )

ぱっと見、ややこしい事はしてない風に見えるので easy 実装で良いのかなぁ。以下。

(define (install-rectangular-package)
 ;; internal procedures
-- 中略 --
 (define (=zero-rect? x) (and (zero? (real-part x)) (zero? (imag-part x))))
-- 中略 --
 (put '=zero? '(rectangular) =zero-rect?)
 'done)

(define (install-polar-package)
 ;; internal procedures
-- 中略 --
 (define (=zero-polar? x) (and (zero? (magnitude x)) (zero? (angle x))))
-- 中略 --
 (put '=zero? '(polar) =zero-polar?)
 'done)

-- 中略 --

(define (install-complex-package)
 ;; imported procedures from rectangular and polar packages
-- 中略 --
 (put '=zero? '(complex) =zero?)
 'done)

なんか自分で書いてて略しすぎでワケワカ。しかも equ? と =zero? が (以下略
一応上記で試験はパス。

2.5.2 節以降、密度が濃い。本職も多忙な上、マルチなソレもあったりハードルが高かったりして、ノルマを設定した方が良いかな、と思っている今日この頃。