「Lisp系言語」というくくりなら、確か1991年頃。古本屋で手に取ったこの本を読んで ちくちくコードをMS-DOSマシンに打ち込んだのがはじまり:
この本のはWiil'o Lispという独自処理系。一応Lisp-2 (変数と関数は別名前空間) だったと思う。
その後処理系に興味を持って、自分で簡単な組み込みLisp処理系を作ったり してた。で、手持ちのDOSマシンでも動かせるLispは無いかしらんと 探して見つけたのがSCM。 だったと思う。それがSchemeとのファーストコンタクト。
上に書いた通り、最初はDOSマシンでも動かせるLispのひとつとして。
Schemeの単一名前空間、統一的な基本ライブラリ、lambda式がそのまま クロージャになること、なんかは実装から入った身としてはとても わかりやすかった。言語仕様が小さいのも魅力で、当時まだ現役だった自作の CP/M-80マシン上で動くSchemeを作ったりして遊んでいた。
良く使っていたのはCとZ-80アセンブリ。 (8086アセンブリにはなぜかそそられなかった。 68000系はあこがれたんだけど、結局ボードコンピュータ作って OS-9/68kを載せる夢は実現しなかったなあ)。
次に使っていたのは授業でやったTurbo PascalとFortran。
今はプログラミング全体のうち3〜7割がSchemeかなあ。(Common Lispも含めると8割)。 とりあえず、以前ならばPerlを使っていたであろう、日常のちょっとした スクリプト書きは完全にScheme (Gauche) に移行した。
ついでなのでLisp/Scheme処理系遍歴をば。
(そういえば、KL1で Scheme処理系を書いたこともあったのを思い出した。)
プログラミングについて感覚的に想起するのは、ロジックという煉瓦を 積んで建物を作るイメージだ。
もっとも、正直に煉瓦をひとつひとつ手で積むのはアセンブリ言語。 高級言語なら抽象化の仕組みを使って大きな部品を組んでおき、 実際の建物はそれらの部品をさくっと組み合わせて作ることになる。 ただ、微視的に見ればそれら部品も結局は基本的なロジックの積み重ねにすぎない。
軽量言語は、ちょっとしたプレハブやツーバイフォーの住宅を さくっと作るのに便利な部品が揃っているとも言えるだろう。 ただ、プログラミングのプロジェクトはそれだけじゃない。 何年もかけて、土台からじっくり作りこまなければならないようなのもある。
47階を作っている時に、2階の煉瓦が重みに耐えられずに崩れてしまったら大変だ。 上から全部壊して積み直すか、2階の壊れた部分に補強を入れて建築を続けるしかない。 煉瓦そのものの強度がやわだと、積めば積むほど補強がごてごて必要になり、 しまいには土台がどこにあるのかわからなくなってしまうかもしれない。
3階の窓枠が気にいらずに直したら、63階のドアがゆがんで開かなくなって しまった、なんてのも困る。たとえ上にどんなに重い構造が載っていても、 骨組み以外の部分は後から自由に直したい。それができないと、 建物が大きくなった時に、誰も下の方の階をいじれなくなる。
頑健性が重要だからって、全ての構造を溶接することを要求されるのも、 また困る。なぜなら今作りかけのフロアについては、自由に色々組み換えて アイディアを試したいからだ。
こういうプロジェクトで信頼できる言語とは、色々なアイディアを試せる 柔軟性と、ちゃんと組めばその上にどれだけロジックが積み重なっても 支えることが出来る丈夫さ、そして荷重がどこにかかるかが見取り易い 単純さ、を備えた言語だ。
Schemeの動的型付け、静的スコープ、 直交性の高い単純なシンタックスとセマンティクス、 これらの条件に当てはまると感じる。
さらにもう一つ、Lisp族言語が備える大事な性質、 いわば、煉瓦そのものの頑丈さに対応する性質がある。
プログラムとは、アイディアの過不足無い記述であるべきだ。 あるプログラム片に込められた意味は、そのプログラム片の字面に 余すところなく表現されていてほしい。
プログラムの意味を決めているのは何だろう。プログラミング言語の セマンティクスだ。言語のセマンティクスが曖昧だと、プログラムの 意味も曖昧になり、プログラム片の字面を信ずることが出来なくなる。 何年もかけてその上に積んだロジックがいつかひっくり返されるかも しれないとなると、そもそもロジックを積んで行こうという意欲が削がれる。
言語のセマンティクスを信頼に足る程度に記述するにはどうすればいいか。 (ここで言っているのはあくまで主観的な信頼であって、表示意味論みたいな 厳密なセマンティクスではない。) プログラミング言語もまた、アイディアの産物であり、 アイディアの記述がプログラミング言語で可能なのならば、 プログラミング言語のセマンティクスをプログラミング言語で 記述してやることは有効だろう。
だが、プログラミング言語Xのセマンティクスをプログラミング言語Yで 記述したとすると、言語Xのセマンティクスは結局言語Yのセマンティクスに 依存する。今度は言語Yを言語Zで記述して…これじゃどこまで行っても きりがない。
…いや、実はきりがある。言語Xのセマンティクスを言語X自身で記述するんだ。 自分で自分を定義するなんて、まるで「空中に浮かぶには自分の足を 自分の手で落ちないように持っていればいい」みたいに変な話に 思えるかもしれない。でもプログラミング言語に関しては、これはうまくいく。 例えばこう解釈すればいい。言語Xのとりあえずの意味を決める。その意味を 使って、言語Xで書かれた言語Xの定義の意味を求める。それが元の仮決めの 意味と矛盾していなければ、そこにひとつの壊すことのできない円環が生じる。 そうして得られる言語Xの意味と定義は、お互いがお互いを支えあっていて、 他のものに影響されない。それが全てを支える土台になるんだ。
(あーもちろんXの無矛盾性をX自身で証明することは出来ないけれど、 プログラムを書く際にはそういう病的なケースを避けて書けばいいだけなんで、 ゲーデルの言ってることとは相反しない)
(なお、XによってX自身の定義を書く、というのは、言語によって言語の定義を 記述する、という操作の不動点を求めているとも言える。)
言語Xによる言語Xの定義の記述は、単純であればある程良い。 チューリング完全な言語であれば自分の処理系を自分で書けるのは自明だが、 それがあまりに複雑だと、その記述自体を頭で追うのがしんどくなる。 そう、このくらい 単純であれば、自分が確かに壊れない円環を手にしているということを 信じることができる。