SICP 読み (51) 2.4.3 データ主導プログラミングと加法性

大切なサケを吸収されたのは問題 2.73 の頁あたり。
ここ、動かん (というか動かせん) とゆーのは苦痛なんで 3.3.3 節から表の手続きをパクって動かしてしまえ。

で、面倒なので SICP のサイトから全部パクった。以下で動くはず。

(define (assoc key records)
 (cond ((null? records) #f)
       ((equal? key (caar records)) (car records))
       (else (assoc key (cdr records)))))

(define (make-table)
 (let ((local-table (list '*table*)))
   (define (lookup key-1 key-2)
     (let ((subtable (assoc key-1 (cdr local-table))))
       (if subtable
           (let ((record (assoc key-2 (cdr subtable))))
             (if record
                 (cdr record)
                 #f))
           #f)))
   (define (insert! key-1 key-2 value)
     (let ((subtable (assoc key-1 (cdr local-table))))
       (if subtable
           (let ((record (assoc key-2 (cdr subtable))))
             (if record
                 (set-cdr! record value)
                 (set-cdr! subtable
                           (cons (cons key-2 value) (cdr subtable)))))
           (set-cdr! local-table
                     (cons (list key-1
                                 (cons key-2 value))
                           (cdr local-table)))))
     'ok)
   (define (dispatch m)
     (cond ((eq? m 'lookup-proc) lookup)
           ((eq? m 'insert-proc!) insert!)
           (else (error "Unknown operation -- TABLE" m))))
   dispatch))

(define operation-table (make-table))
(define get (operation-table 'lookup-proc))
(define put (operation-table 'insert-proc!))

(define (attach-tag type-tag contents)
 (cons type-tag contents))
(define (type-tag datum)
 (if (pair? datum)
     (car datum)
     (error "Bad tagged datum -- TYPE TAG" datum)))
(define (contents datum)
 (if (pair? datum)
     (cdr datum)
     (error "Bad tagged datum -- CONTENTS" datum)))

(define (install-rectangular-package)
 ;; internal procedures
 (define (real-part z) (car z))
 (define (imag-part z) (cdr z))
 (define (make-from-real-imag x y) (cons x y))
 (define (magnitude z)
   (sqrt (+ (square (real-part z))
            (square (imag-part z)))))
 (define (angle z)
   (atan (imag-part z) (real-part z)))
 (define (make-from-mag-ang r a)
   (cons (* r (cos a)) (* r (sin a))))
 ;; interface to the rest of the system
 (define (tag x) (attach-tag 'rectangular x))
 (put 'real-part '(rectangular) real-part)
 (put 'imag-part '(rectangular) imag-part)
 (put 'magnitude '(rectangular) magnitude)
 (put 'angle '(rectangular) angle)
 (put 'make-from-real-imag 'rectangular
      (lambda (x y) (tag (make-from-real-imag x y))))
 (put 'make-from-mag-ang 'rectangular
      (lambda (r a) (tag (make-from-mag-ang r a))))
 'done)

(define (install-polar-package)
 ;; internal procedures
 (define (magnitude z) (car z))
 (define (angle z) (cdr z))
 (define (make-from-mag-ang r a) (cons r a))
 (define (real-part z)
   (* (magnitude z) (cos (angle z))))
 (define (imag-part z)
   (* (magnitude z) (sin (angle z))))
 (define (make-from-real-imag x y)
   (cons (sqrt (+ (square x) (square y)))
         (atan y x)))
 ;; interface to the rest of the system
 (define (tag x) (attach-tag 'polar x))
 (put 'real-part '(polar) real-part)
 (put 'imag-part '(polar) imag-part)
 (put 'magnitude '(polar) magnitude)
 (put 'angle '(polar) angle)
 (put 'make-from-real-imag 'polar
      (lambda (x y) (tag (make-from-real-imag x y))))
 (put 'make-from-mag-ang 'polar
      (lambda (r a) (tag (make-from-mag-ang r a))))
 'done)

(define (apply-generic op . args)
 (let ((type-tags (map type-tag args)))
   (let ((proc (get op type-tags)))
     (if proc
         (apply proc (map contents args))
         (error
           "No method for these types -- APPLY-GENERIC"
           (list op type-tags))))))

(define (real-part z) (apply-generic 'real-part z))
(define (imag-part z) (apply-generic 'imag-part z))
(define (magnitude z) (apply-generic 'magnitude z))
(define (angle z) (apply-generic 'angle z))

(define (make-from-real-imag x y)
 ((get 'make-from-real-imag 'rectangular) x y))
(define (make-from-mag-ang r a)
 ((get 'make-from-mag-ang 'polar) r a))

で、これから試験を検討するんですが、表の関係の試験は略そう。

とりあえず、こんな試験を書いた。

 (let ((obj (make-from-real-imag (sqrt 3) 1.0)))
   (assert-equal (marume (sqrt 3))
                 (marume (apply-generic 'real-part obj))))

で、動かしてみると

$ test/run-test.scm -vv
- (test suite) 2.4.3
-- (test case) apply-generic test: E
./test/test-2.4.3.scm:16: (make-from-real-imag (sqrt 3) 1.0)
Error occurred in apply-generic test
*** ERROR: invalid application: (#f 1.7320508075688772 1.0)
./test/test-2.4.3.scm:16: (make-from-real-imag (sqrt 3) 1.0)


1 tests, 1 assertions, 0 successes, 0 failures, 1 errors
Testing time: 0
$

あら、lookup がきちんと動いてないの?? と思ったらパケジを install する手続きを呼びださなければ、な事に気がつく (とほほほ

で、こう。

 (let ((rect (install-rectangular-package))
       (pol (install-polar-package)))
   (let ((obj (make-from-real-imag (sqrt 3) 1.0)))
     (assert-equal (marume (sqrt 3))
                   (marume (apply-generic 'real-part obj)))))

で、試験。

$ test/run-test.scm -vv
- (test suite) 2.4.3
-- (test case) apply-generic test: .

1 tests, 1 assertions, 1 successes, 0 failures, 0 errors
Testing time: 0.014618
$

を、動いたぞ。いちいち let で install するのは面倒なので setup を使ってみる。

(define-test-suite "2.4.3"

("apply-generic test"
 (setup (lambda ()
         (install-rectangular-package)
         (install-polar-package)))
 ("apply-generic test"
 (let ((obj (make-from-real-imag (sqrt 3) 1.0)))
   (assert-equal (marume (sqrt 3))
                 (marume (apply-generic 'real-part obj))))
 )
)

)

ちゃんと動いている模様。そろそろきちんと試験を検討する事に。

微妙な点を以下に列挙

  • p.106 の註釈にある「記号 rectangular でなく、リスト (rectangular) を使ったのは、同一型でない複数個の引数の演算があるかも知れないためだ。」というもの。
  • 2.4.2 において attach-tag と type-tag と contents の試験をしていない
  • apply-generic の試験、ポイント高い。
  • ((get 'make-from-real-imag 'rectangular) x y) は何を戻すのか

という事で、とりあえず attach-tag と type-tag と contents の試験を。確認するのは以下で良いか。

  • attach-tag
    • attach-tag の戻りを car したら attach-tag の第一引数と同じ値であること
    • attach-tag の戻りを cdr したら attach-tag の第二引数と同じ値であること
  • type-tag
    • ペアでないものを渡したら例外発生
    • 渡したリストの car と同値が戻されていること
  • contesnts
    • ペアでないものを渡したら例外発生
    • 渡したリストの cdr と同値が戻されていること

試験は以下。

("attach-tag test"
 ("attach-tag test"
  (assert-equal 'a (car (attach-tag 'a '(b c d))))
  (assert-equal '(b c d) (cdr (attach-tag 'a '(b c d))))
  )
)

("type-tag test"
 ("type-tag test"
  (assert-error (lambda () (type-tag 'a)))
  (assert-equal 'a (type-tag (cons 'a '(b c d))))
  )
)

("contents test"
 ("contents test"
  (assert-error (lambda () (contents 'a)))
  (assert-equal '(b c d) (contents (cons 'a '(b c d))))
  )
)

次、apply-generic の試験について。
微妙なのが、args を map している部分。こんな手続きを作って様子を見てみる。

gosh> (define (test a . b)
  (define (p x)
    (display x)
    (newline))
  (p a)
  (p b))
test
gosh>

で、こうしてみる。

gosh> (test 'real-part ((get 'make-from-real-imag 'rectangular) (sqrt 3) 1.0))
real-part
((rectangular 1.7320508075688772 . 1.0))
#<undef>
gosh>

とか

gosh> (test 'real-part ((get 'make-from-mag-ang 'polar) 2 (/ pi 6)))
real-part
((polar 2 . 0.5235987755982988))
#<undef>
gosh>

とか。args はリストのリストになっている。これを type-tag で map したらどうなるのか。

gosh> (map type-tag '((polar 2 . 0.5235987755982988)))
(polar)
gosh> (map type-tag '((polar 2 . 0.5235987755982988) (rectangular 1.7320508075688772 . 1.0)))
(polar rectangular)
gosh>

むむ。あるいは

gosh> (map contents '((polar 2 . 0.5235987755982988)))
((2 . 0.5235987755982988))
gosh> (map contents '((polar 2 . 0.5235987755982988) (rectangular 1.7320508075688772 . 1.0)))
((2 . 0.5235987755982988) (1.7320508075688772 . 1.0))
gosh>

したらこれは??

gosh> (get 'real-part '(polar))
#<closure (install-polar-package real-part)>
gosh>

手続きが戻ってきた。当たり前か。最終的に適用されるのがこんな感じですか。

gosh> (apply (get 'real-part '(polar)) (map contents '((polar 2 . 0.5235987755982988))))
1.7320508075688774
gosh>

備忘録までもう少し

gosh> (apply 
       (get 'real-part 
	    (map type-tag '((polar 2 . 0.5235987755982988)))) 
       (map contents '((polar 2 . 0.5235987755982988))))
1.7320508075688774
gosh> 

手続きの定義を見るに、map を使って手続きを取り出したり、渡す引数を取り出しているのは註釈にもあるように_ドット末尾記法_を使っているから??
註釈 45 とか 47 にある「同一型でない複数個の引数があるかも知れない」とか「異る汎用演算は引数の個数が異るかも知れぬ」というソレが今ひとつ理解できてないなぁ。

とりあえず意味的にそのまんまと判断して試験を検討。試験としては

  • put した手続きが呼び出されている
  • put されていない場合、error
  • 異る型の複数の引数を使用するケース

という事で以下。なんと言えば良いか分かりませんが微妙。

("apply-generic test"
 (setup (lambda ()
	  (define (install-test-package)
	    (define (square x) (* x x))
	    (define (rect-mag x y)
	      (sqrt (+ (square (car x))
		       (square (cdr x)))))
	    (define (polar-mag x y) (car y))
	    (put 'rect-mag '(rectangular polar) rect-mag)
	    (put 'polar-mag '(rectangular polar) polar-mag)
	    'done)
	  (install-test-package)
	  (install-rectangular-package)
	  (install-polar-package)))
 ("apply-generic test"
  (let ((obj (make-from-real-imag (sqrt 3) 1.0)))
    (assert-equal '(rectangular)
		  (map type-tag '((rectangular 1.7320508075688772 . 1.0))))
    (assert-equal '(rectangular polar)
		  (map type-tag '((rectangular 1.7320508075688772 . 1.0)
				  (polar 2 . 0.5235987755982988))))
    (assert-equal '((1.7320508075688772 . 1.0))
		  (map contents '((rectangular 1.7320508075688772 . 1.0))))
    (assert-equal '((1.7320508075688772 . 1.0) (2 . 0.5235987755982988))
		  (map contents '((rectangular 1.7320508075688772 . 1.0)
				  (polar 2 . 0.5235987755982988))))
    (assert-equal (sqrt 3)
		  (apply-generic 'real-part obj))
    (assert-equal 2.0
		  (apply-generic 'rect-mag
				 '(rectangular 1.7320508075688772 . 1.0)
				 '(polar 2 . 0.5235987755982988)))
    (assert-equal 2
		  (apply-generic 'polar-mag
				 '(rectangular 1.7320508075688772 . 1.0)
				 '(polar 2 . 0.5235987755982988)))
    (assert-error (lambda ()(apply-generic 'xxx obj))))
 )
)

一応複数引数で異る型にも対応している。他の試験は略。