SICP 読み (170) 4.1.6 内部定義

問題 4.16 に逆もどり。よく考えると根拠の無い推論ではなくて、実機で検証できる事に気がついた次第で。どこまでヤレるか分かりませんが再チャレンジとゆー事で。
と言いつつ以前のエントリを検索かけてみると、似たようなコトしてますが、気にせずリトライ。おそらく同じ手続きを試験してると思いますが、以下を順に確認してみる。

(define (f x)
  (define (even? n)
    (if (= n 0)
	true
	(odd? (- n 1))))
  (define (odd? n)
    (if (= n 0)
	false
	(even? (- n 1))))
  (even? x))

定義

まず上記の式が eval されるサマを試験で確認。上記の式を l に代入。

      (assert-true (definition? l))

definition 認定。

      (assert-equal 'f (definition-variable l))
      (let ((ll '(lambda (x)
		   (define (even? n)
		     (if (= n 0)
			 true
			 (odd? (- n 1))))
		   (define (odd? n)
		     (if (= n 0)
			 false
			 (even? (- n 1))))
		   (even? x))))
	(assert-equal ll (definition-value l))
	)

これ風に取り出されて、definition-value が eval される。で、その definition-value は上記の通り、lambda 認定のはず。

      (assert-true (lambda? (definition-value l)))

あるいは make-procedure に渡されるナニは

      (assert-equal '(x) (lambda-parameters (definition-value l)))
      (let ((ll '((define (even? n)
		    (if (= n 0)
			true
			(odd? (- n 1))))
		  (define (odd? n)
		    (if (= n 0)
			false
			(even? (- n 1))))
		  (even? x))))
	(assert-equal ll (lambda-body (definition-value l)))
	)

こんなソレ達が渡される、と。
で、make-procedure に scan-out-defines が仕込んであった場合には上記の ll が変換対象となる訳です。

      (let ((ll '((let ((even? '*unassigned*)
			(odd? '*unassigned*))
		    (set! even? (lambda (n)
				  (if (= n 0)
				      true
				      (odd? (- n 1)))))
		    (set! odd? (lambda (n)
				 (if (= n 0)
				     false
				     (even? (- n 1)))))
		    (even? x)))))
	(assert-equal ll (scan-out-defines
			  (lambda-body
			   (definition-value l))))
	)

これは現時点では余計か。ただ、一番上の define な式が eval されたらば f に束縛されるのは scan-out で変換される前か後の式が body に据えられた procedure で始まるリストになる、という理解で大丈夫なはず。

      (let ((newenv (extend-environment '(tmp) '(0) the-global-environment)))
	(eval l newenv)
	(assert-equal 'procedure (car (lookup-variable-value 'f newenv)))
	(assert-equal '(x) (cadr (lookup-variable-value 'f newenv)))
	(let ((ll '((define (even? n)
		      (if (= n 0)
			  true
			  (odd? (- n 1))))
		    (define (odd? n)
		      (if (= n 0)
			  false
			  (even? (- n 1))))
		    (even? x))))
	  (assert-equal ll (caddr (lookup-variable-value 'f newenv)))
	  )
	)

あるいは make-procedure に scan-out-defines が仕込まれていた場合には

      (let ((newenv (extend-environment '(tmp) '(0) the-global-environment)))
	(let ((dv (definition-value l)))
	  (let ((ll (make-procedure (lambda-parameters dv)
				    (scan-out-defines
				     (lambda-body dv))
				    newenv)))
	    (define-variable! (definition-variable l) ll newenv)
	    (let ((lll (lookup-variable-value 'f newenv)))
	      (assert-equal 'procedure (car lll))
	      (assert-equal '(x) (cadr lll))
	      (let ((llll '((let ((even? '*unassigned*)
				  (odd? '*unassigned*))
			      (set! even? (lambda (n)
					    (if (= n 0)
						true
						(odd? (- n 1)))))
			      (set! odd? (lambda (n)
					   (if (= n 0)
					       false
					       (even? (- n 1)))))
			      (even? x)))))
		(assert-equal llll (caddr lll))
		)
	      )
	    )
	  )
	)

こうなる、と。ってか手動で eval してるぞ。本当に当ってるんだろうか。(何
とりあえず定義式が eval された場合、どうなるかとゆーのは確認できてるハズ。って同じトコを repeat してる感満点だなぁ。結局確認できた後に何も残らんオチな予感。これ的な問題って分かってれば明白な根拠があるはずなんだけどなぁ。それが見えんのが無償にハラ立つ。

適用

さらに apply を手動で。上記によれば一連の手続きは f に束縛されているので

(f 2)

を eval してみれば良いのか。scan-out-defines しないパターンが以下。

    (let ((newenv (extend-environment '(tmp) '(0) the-global-environment)))
      (eval '(define (f x)
	       (define (even? n)
		 (if (= n 0)
		     true
		     (odd? (- n 1))))
	       (define (odd? n)
		 (if (= n 0)
		     false
		     (even? (- n 1))))
	       (even? x)) newenv)
      (assert-equal 'procedure (car (lookup-variable-value 'f newenv)))
      (let ((l '(f 2)))
	(assert-true (compound-procedure? (eval (operator l) newenv)))
	(let ((ll '((define (even? n)
		      (if (= n 0)
			  true
			  (odd? (- n 1))))
		    (define (odd? n)
		      (if (= n 0)
			  false
			  (even? (- n 1))))
		    (even? x))))
	  (assert-equal ll (procedure-body (eval (operator l) newenv)))
	  )
	)
      )

なんとなく問題なのはこの先なのかなぁ、とも。
なんかへろへろで帰宅して同じコトして時間切れになってる感じがするなぁ。
ちなみに apply で scan-out-defines する場合は上の procedure-body の中で手続きが適用されるはず。ただ、どちらにしても、eval-sequence に渡る時には同じ状態になっているはずです。
速度な観点から言えば

  • eval 側に scan-out-defines が組込まれていた場合には eval した時点で procedure-body は let で始まる式に変換されている
  • apply 側に scan-out-defines が組込まれていた場合には apply した後の eval-sequence に渡る直前に let で始まる式に変換される

なんですが、どちらにしても apply で let を lambda に変換しなきゃいけないので、その間のコスト等々を鑑みるに、手続きを適用する前に scan-out-defines で変換されていた方が良いのかなぁ。これって問題ぱっと見で直感で感じたコトなだけにとても微妙。

別途もう少しツッコンでみたいと思いますが、大ウソがあったら遠慮なくツッコンでもらえれば幸いです。> 識者な方々