言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

3imp 写経の日々その2(マニアむけ)

というわけでやっと 4.7 章まで読み終えた感想です。

3imp は特に Lisp 系言語の利点を最大限に発揮した構成になっています。この限られたスペースの中で、コンパイラの実装について不足なく説明しきる事が出来た勝因は S 式とマクロでしょう。私は正直 S 式もマクロも嫌いですが、ことプログラミング言語の実装を解説するという用途において Lisp は最強という感想を持ちました。

まず S 式について。S 式を使うとパーサが不要です。世の中に出回っているコンパイラの教科書のかなりのスペースがパーサの説明になっている事を考えると、パーサが不要というのは画期的です。パーサの説明を省く事によっていきなり本質的な議論が出来るからです。

それからマクロについて。3imp のソースは SICP に比べて相当分かりやすいですが、その理由は record-case マクロによるパターンマッチもどきにあると思います。record-case は、いわゆる switch-case 文のように使えるマクロです。あるリストの最初の要素でマッチして、残りを変数に束縛します。実装例はこんな感じです。

(define-syntax record-case
  (syntax-rules (else)
    ((record-case exp1 [else expr3 ...])
     (begin expr3 ...))
    ((record-case exp1 [key vars exp2 ...] case2 ...)
     (let ([r exp1])
       (if (eq? (car r) (quote key))
	   (record vars (cdr r) exp2 ...)
	   (record-case exp1 case2 ...))))))

これを使うと、単純なパターンマッチで簡単な計算器を作る事が出来ます。この例では、例えば x に (add 3 4) のようなリストがやって来た時、record-case 無いの最初の条件にマッチし、その中の x, y にそれぞれ 3, 4 が束縛されます。

(define calc
  (lambda (x)
    (if (integer? x)
	x
	(record-case x
	   (add (x y) (+ (calc x) (calc y)))
	   (mul (x y) (* (calc x) (calc y)))
	   (neg (x) (- 0 (calc x)))
	   (else (error "invalid expression"))))))

(calc '(neg (mul (add 3 4) 6))) => -42

これで cadr とか caadr とかやるよりも無茶苦茶コードが読みやすくなります。マクロの定義が簡単なのも良いです。

4.7 章までの中で一番面白かったのは、4.4 章の自由変数を抜き出す部分です。この章では、スタックマシンを使ってクロージャを実装するために find-free 関数を使ってソースを静的に検査して自由変数を抜き出し、そこだけクロージャの要素としてコピーします。するとクロージャは単に関数定義とコピーした自由変数の組として表現出来るので、スタック上に置いても大丈夫です。そんな事をして、外のスコープで自由変数が更新されたらどうなるんだと心配になりますが、ちゃんと次の章で解決されます。このように一度に一つずつ問題が解決されて行くスタイルも読みやすいです。

惜しむらくはソースコードに単純ミスが多い事です。まあこれも、ちゃんと読んだかどうか確かめる為に作者が仕込んだ罠だと思えば面白いかもしれません。

今までの写経の記録は http://github.com/propella/3imp-westwood にあります。