クロージャーの実装

自作 Lisp インタプリタ続き。クロージャーを実装してみた。まだいろいろと細かい点が怪しいので、あとで HyperSpec とにらめっこして調べる。特にラムダ式の場合が怪しい、というより、この処理系には funcall がまだ無いので、試せない。TODO.

  • defun による関数定義は、必ずトップレベルに束縛するように変更。
  • トップレベルでないところで定義された関数は、シンボルに環境を保存。
  • 関数評価時に環境が保存されていたら、その環境で評価。

以下が通るようになったテスト。まだ何か勘違いしていそうで怖いが、、

(with-tests (:name "closure")
  (test 5 (with-toy ()
	    (let ((x 5)) ;; closure
	      (defun f() x))
	    (let ((x 3)) ;; dynamic binding ではないのでこの x は参照されず、f を定義したときの x = 5 が返る。
	      (f))))
  (test 80 (with-toy ()
	      (let ((balance 0)) ;; balance を共有する二つの関数 deposit と withdraw を定義。
		(defun deposit (amount)
		  (setq balance (+ balance amount)))
		(defun withdraw (amount)
		  (setq balance (- balance amount))))
	      (setq balance 5000) ;; これは global なので、deposit/withdraw とは無関係。
	      (deposit 100)
	      (withdraw 50)
	      (setq balance 10000) ;; 同じく無関係。
	      (deposit 30)))) ;; 0 + 100 - 50 + 30 の結果である 80 が返る。


既に自分で作ったインタプリタながら、頭で追える領域を超えているなぁ。
この感覚は再帰関数を初めて学んだときの混乱に似ているような気がする。再帰の初学者は、ついつい頭の中に無限の関数呼び出しみたいなものを想像して、訳が分からなくなる。ちょっと慣れると、再帰は単なる定義として割り切って、無限の階層を考えたりしなくなる。


ホンモノの言語処理系を作っているヒトも、そういうふうにうまく割り切って、自分のプログラムを捉えているのだろうか?
それとも訓練と才能による、自分には想像つかないような特別な能力によって処理系を捉えているのだろうか?