2010/06/05
分けて考える
「評価と実行を分けて考える」というのがうまい表現だなと思った。
本来、別々に考えられる概念XとYとが、実装の都合から たまたまいっしょくたになっていることがある。 そういうモデルを先に学んでしまうと、 XとYを別々に扱うモデルに出会ったとき、ちんぷんかんぷんに思える。 新しい何かを理解しようとするとき、 人はまず自分が知っているモデルに当てはめようとするからだ。 自分の中のモデルでXとYが区別されていないと、 対象のXとYをうまく自分の中に対応づけられないので混乱してしまう。
理解の鍵は、自分の中のモデルとの対応関係を探しつづけるのをあきらめて、 逆に出会った概念でもって自分の中のモデルを再構築してみることかもしれない。
今まで理解してきたことを振り返ると、このパターンに何度も出会っている気がする。
インタプリタとコンパイラはそのひとつだった。電卓プログラムまではわりと簡単にたどり着けて、 インタプリタの動作は納得できたんだけれど、そこからコンパイラが何をやっているかを 理解するのにひとつハードルがあった気がする。 ここで分ける必要があったのは、プログラムの「解釈」と「評価実行」だったと言える。
C言語にどっぷり浸かった後でのLisp/Schemeのクロージャの理解にも、 似たようなハードルがあった。C言語でのauto変数は、 ローカル変数のスコープとエクステントが一緒になっている。 自分はそれをスタックモデルで理解していた。 C言語でもstaticを使えば一応ローカルスコープで無限エクステントな 変数になるけれど、それはdata領域に静的に確保されてるから、 当時の理解としてはグローバル変数の仲間という感覚。 つまり、静的に確保されるメモリとスタック、というモデルで理解してたもんだから、 動的に作られてローカルスコープなんだけど無限エクステントな変数、 というのがよく分からなかった。
Schemeの継続も、制御フロー、アクティベーションレコード、スタック という要素を分けて考えられるまで、理解に苦労した。
Lisperは、プログラムの構文解析(read)と「解釈評価実行」(eval)を分けて考えるのを 当然だと思っているけれど、evalを持つ動的言語でこの二つが分かれているものは あまり多くない。JSONが現れた時、簡易プログラムがいきなり受け取った文字列を evalしているのを見てびっくりしたものだ。そしたら今度はevalは危ないからと わざわざパーズしたりしている。ひどく遠回りをしているように見えたものだ。
Lisp初心者が、「シンボル」の存在につまづくことがある。 プログラムをプログラムとして見ているだけでは、シンボルは不必要だ。 C言語のソースコード上の int x; というのはあくまでソース上に記された記号であって、 プログラムの実行中に触れる対象ではない。プログラムをデータとして読み込むことで、 ソース上の同じ記号に、 「プログラムとして見た場合の識別子」と「データとして見た場合のシンボル」という 二つの違った意味が付加される。
実は、Schemeのhygienic macroのとっつきにくさ、というのも、 「シンボル」と「シンボルが示すもの」を分けて考えることに起因しているように思える。 たとえば下のコードにはxが4回現れるけれど:
(define x 10) (let ((x (+ x 1))) (* x 2))
区別するために番号をふって:
(define x{0} 10) (let ((x{1} (+ x{2} 1))) (* x{3} 2))
Lisperは、x{0}とx{2}があるグローバルな変数を示していて、 x{1}とx{3}はローカルな変数を示している、ことを直ちに了解するけれど、 あんまりに自明なものだから、それらにxという共通のシンボルを使うことを 不思議に思わない。けれども、マクロによってスコープを超えて識別子の挿入が 起きると、「シンボル」と「シンボルが示すもの」を意識する必要が出てくる。
(define x{0} 10) (define-syntax k (syntax-rules () ((_) x{4}))) (let ((x{1} (+ x{2} 1))) (* x{3} (k)))
この場合、(k)の展開によって出てくるx{4}は、x{0}が示すものと同じものを 示すべきか、x{1}が示すものと同じものを示すべきか。 Schemeのhygienic macroは、「シンボルが示すもの」について一貫している べきだとの立場をとる。つまり、x{4}はそれが出現するところのスコープによる 規則に従ってx{0}と同じものを示す。シンボルだけを考えていると hygienic macroが何をやっているのかよくわからなくなる。
今、自分の中で分離の必要を感じているのは、「値」と「型」と「評価」かなあ。 Lispでコードを書いている時に、例えば (foo <...>) という式を見たとすると (<...>の中は適当な式)、 この時自分の頭の中では、まず <...> が評価された「値」というのが存在して、 それにfooを適用する、というふうに考える。でもこうやって「そこにあるはずの値」 を追っかけて行く方法だと、ポイントフリースタイルみたいに引数を省いて 関数の組み合わせだけで書かれたコードが理解できない。 あと、「型」もまず手元に「値」ありきで考えちゃうので、 型だけの組み合わせで考えを進めるのが苦手。 多分、頭の中の実在感のとっかかりを「値」から引き離すことが必要だろうなと感じている。
Tag: Programming
ayato (2010/06/06 08:56:26):
shiro (2010/06/11 00:07:54):