空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

etolisp 進捗 (21) 〜 Packageの実装 (4)

朝起きると神のお告げがあり,etolispはまたも邪悪な「俺仕様」に陥らずに済んだ。イヤ,結局,仕様に当たったわけではないんだけど,私の好ましい方を支持する指摘があったので,そっちを信じることにした(『神のお告げ』たる所以・笑)。name ID とか symbol とか qymbol とか,ヘンなものをいろいろ発明したけど,qymbol を棄てることができて,少なくとも実装はとてもスッキリしたものになった。qymbol の残骸を整理していて,我ながら,間違った仕様であっても何とか実装して動かしてしまう自分を誉めてあげたいと思った(幸せな奴だ)。

パース時にシンボルが全て決定するということは,パース自体がランタイムの状態に sensitive であるということである。だから,ランタイムの状態を変化させうる命令は,パースされた直後に実行しなければならない。で,パースの単位って何だろうと考えてみたが,それは恐らく,コッカで最初に開いたカッコが閉じるまでなんだろう。今日の修正で,今までファイル全体をパースしてから実行していたのを,カッコ&コッカ単位で実行して使い捨てるようになった。お陰でメモリも節約できた。てゆーか明らかに以前の方法は無駄遣いだった。

いやしかし,Common-Lisp の仕様はよく実装のことを考えてる気がするよ,仕様に則るだけで処理系の実装が綺麗になるよ,と自分のミスを他人のファインプレーに転嫁してみる。

ただ,双手を上げて完璧であると誉めているわけではない。たとえば,

(setf *package* (make-package 'mine :use '(common-lisp)))
(defun foo () (in-package common-lisp-user) (setf mine-a 1234))
(foo)
(mine::foo)

と書いたとき,mine-a は mine::mine-a であって,一度目の (foo) の実行でcurrent packageがcommon-lisp-userに変わることはあっても,(foo) でも (mine::foo) でも,defun のパース時の current package が mine なので,setf されるのは mine::mine-a である。だから,この結果はたんに関数を手でインラインに書き下したものとは異なる結果を与えるという,気持ち悪い現象が起こる。これは名前空間の仕組みを,プログラムの静的な構造ではなくて,動的なLispの命令で実現してしまったところに原因があるのだと思う。

でも,プログラムの静的な構造で実現するとしても,Lispでやるとしたらカッコとコッカしかないわけで,パッケージ全体を巨大な1つのカッコ&コッカで括るか,あるpackageに属することを,各パース単位毎に指示しなければならず,コード自体のみやすさが損なわれる可能性がある。

コードを読み書きしていて思ったのは,Lispのコーディングでは「今どのカッコが開いているか」を脳内スタックに積みながら cons の構造を読み解いていくということ。コッカは編集対象の構造の先頭が閉じるまで,あとづけで足していくに過ぎない。その際,ファイルの先頭ででっかくカッコが開いているとそれだけスタックを無駄遣いすることになるわけで,プログラマはそれを嫌うかも知れない(たんなる想像)。

更なる妄想だが,Common-Lispが少しずつ発展してきたことを想像すると,packageの機能が新たに付け加えられたとき,既存のコードの先頭に1行足すだけでpackage化でき,名前空間で保護できるという利点は,過去の資産を引き継ぐ上で都合が良かったことだろう。

以上のように考えると,この仕様は現実的な妥協ラインのように思える。