第1章拡張可能なプログラミング言語The Extensible Language遠くない昔,Lisp は何のためのプログラミング言語かと尋ねれば,多くの人が「人工知能(AI) 用のプログラミング言語」と答えただろう.実際はLisp とAIとの関係は歴史の偶然にすぎない.Lisp の開発者はJohn McCarthy で,彼は「人工知能」という言葉の提唱者でもある.彼の学生と同僚たちはプログラムをLisp で書いた.それがLisp がAI 用のプログラミング言語と言われ出したきっかけだ.このつながりは広く取り上げられ,1980 年代の短いAI ブームの間に大変なほど繰り返されたので,ほとんど迷信のようになってしまった.幸運なことに,AI だけがLisp の目的でないとの言葉が広まり始めた.最近のハードウェアとソフトウェアの進歩のおかげで,Lisp は商業的にも成功し始めた:今では最高のUnix 系テキストエディタGNU Emacs,業界標準のデスクトップCAD ソフトAutocad,そして先駆的ハイエンド出版ソフトInterleaf で使われている.これらのプログラムでのLisp の使われ方はAI とは何も関係ない.Lisp がAI 用プログラミング言語でないのなら,いったい何なのか? Lisp をその交友関係から判断するのでなく,言語そのものを見てみよう.他のプログラミング言語でできないことのうち,Lisp には何ができるか? Lisp の最も特徴的な性質の一つは,書こうとしているプログラムに合わせてLisp を仕立てることができる点だ.LispそのものがLisp プログラムであること,Lisp のプログラムはリストとして表現でき,リストはLisp のデータ構造だということ.これら2 個の原則が相俟って,組み込みのものと区別のつかないオペレータをどのユーザもLisp に追加できることになる.1.1 進化によるデザインLisp では独自のオペレータを定義する自由がユーザにあるので,Lisp を必要なプログラミング言語にきっちり仕立てることができる.ユーザがテキストエディタのプログラムを書いているのなら,Lisp をテキストエディタを書くための言語に変えることができる.またCAD ソフトのプログラムを書いているのなら,Lisp をCAD ソフトを書くための言語に変えることもできる.そしてどんなプログラムを書くかまだ確かでないなら,Lisp で書いておくのが安全な賭けだ.それがどんな種類のプログラムになったとしても,それを書いている間に,Lisp はその種類のプログラムを書くためのプログラミング言語に進化していることだろう.どんなプログラムを書くかまだ確かでないなら? 人によってはこの文は奇妙に響くだろう.これは(1)これからやることを注意深く計画し,そして(2) それを実行する,というモデルとは水と油のように相容れない考え方だ.プログラムのすべき動作を決める前にプログラムを書くことをLisp が勧めているとすれば,このモデルによれば,Lisp は単に杜撰な考え方を勧めているにすぎないことになる.だが,そんなことはない.「計画−実装」方式もダム建設や侵略作戦の決行にはいい方法だったが,人々の経験によればプログラムを書くのにいい方法だったかどうかは定かでない.なぜか? きっと,コンピュータは大変正確だからだ.きっと,プログラムはダムや侵略作戦よりもバリエーションが多いからだ.またはきっと,古い概念における冗長性に相当するものがソフトウェア開発にないせいで,古い方式が機能しないことによるのだろう:ダムが30% 余分なコンクリートを使っていても,それは誤差として許される範囲内だろう.しかしプログラムが30% 余分な動作をしていたら,それは間違いだ.古い方式が失敗に終わるのがなぜかを言うのは難しいかもしれないが,それが確かに失敗しているのは,誰の目にも明らかだ.ソフトウェアが期日どおりに完成したことがあるだろうか? 熟練プログラマは,どれほど慎重にプログラムの計画を立てても,プログラムを書き始めると,必ず計画にどこか不完全な点が見つかることを知っている.計画が望みのないほどまで間違っていることもある.しかし「計画−実装」方式の犠牲者のほとんどはその基礎の健全さを疑おうとはしない.代わりに彼らは人間の失敗を責める:「もう少しいい展望の下に計画を立ててさえいたら,こんな問題はすべて避けられただろう.」最高レベルのプログラマでさえ実装となれば問題に突き当たるのだから,人々にそれほどの先見の明を期待するのはどうやら無茶のようだ.きっと「計画−実装」方式は,我々の持つ限界により適した別のアプローチで置き換えられるのではないか.適切なツールがあるならば,プログラミングへの別のアプローチが可能だ.なぜ実装の前に計画を立てるのか? プロジェクトの立案にどっぷり浸かり込むことの大きな危険性は,自らを袋小路に追い込んでしまう可能性がある点だ.もっと柔軟なプログラミング言語があれば,この心配を減らせるのでは? そのとおり.そしてそういうプログラミング言語がある.Lispの柔軟性は全く新しいプログラミングのスタイルを生み出した.Lisp においては,計画の大部分を立てるのはプログラムを書きながらでいい.なぜ後知恵が浮かぶのを待つのか? モンテーニュ?1が気づいたように,考えを明確にするにはそれを書き下ろそうとすることが一番だ.自らを袋小路に追い込んでしまうとの心配からひとたび解放されれば,この可能性を最大限に活用できる.プログラムを書きながら計画を立てる能力は2つの重要な結果につながる:まず,プログラムを書くのにかかる時間が短くなる.計画を立てると同時にプログラムを書いていると,注意を集中すべきプログラムの実物を常に手にしていることになるからだ.そしてその方法で出来たプログラムはより良いものになるだろう.プログラムの最終デザインが常に進化の産物として出来上がるからだ.プログラムがあるべき姿を探している間に,一つの原則間違っていた部分を,見つけ次第その場で必ず書き直すを守る限り,最後に出来上がったものは,あらかじめ計画に数週間を費やした場合よりも優美なプログラムになるだろう.Lisp が多目的なプログラミング言語だからこそ,この種のプログラミングが実用的な代替手段になる.事実,Lisp の持つ最大の危険はLisp がユーザを堕落させてしまうかもしれないことだ.一度Lisp をしばらく使うと,プログラミング言語とアプリケーションとの相性に敏感になりすぎ,元々使っていたプログラミング言語に戻っても,これでは必要な柔軟性が手に入らないという思いに常に囚われるようになりかねない.1.2 ボトムアッププログラミングプログラムの個々の機能的要素をあまり大きくしすぎてはならないというのが,プログラミングスタイルの長年の原則だ.プログラムの構成要素が,読めば理解できる状態を超えて肥大化するなら,それは複雑さの塊に成り果て,(大都市が流れ者を隠すように)簡単にエラーを覆い隠す.そういうソフトウェアは読みづらく,テストしづらく,デバッグしづらい.この原則に従い,大規模なプログラムは部品へと分割しなければいけない.またプログラムが大規模であればあるほど,さらに分割しなければいけない.プログラムを分割する方法とは何か?伝統的アプローチはトップダウンデザインと呼ばれる:「このプログラムの目的はこれらの7 個だから,プログラムを7 個の主要サブルーチンに分割することにする.1 個目のサブルーチンはこれら4 個のことを行わなければいけないから,それ自身のサブルーチンが今度は4 個になり,. . . 」といった具合だ.このプロセスはプログラム全体が適切な粒度すべての部分が意味のある仕事を行えるだけの大きさでありながら,単一のユニットとして理解できるくらい小さくなった状態になるまで続く.熟練Lispプログラマがプログラムを分割する方法は違っている.トップダウンデザインと同様,彼らには従う原則があり,それはボトムアップデザインプログラミング言語を問題に適するように変えていくと呼ばれる.Lispでは,プログラムをただプログラミング言語に従って書くことはしない.プログラミング言語を自分の書くプログラムに向けて構築するのだ.プログラムを書いているとき,「Lisp に○△のオペレータがあればなあ.」と思うことがあるかもしれない.そうしたらそれを書けばいい.後で,新しいオペレータの使用がプログラムの別の部分のデザインを簡潔にまとめることにつながったと気づくだろう.そういった感じでプログラミング言語とプログラムは共に進化する.交戦中の2 国間の国境のように,2 つの境界は何度も書き直される.それが落ち着くのは,山や川(ここではユーザの問題の持つ自然な境界)に辿り着いたときだ.最終的には,ユーザのプログラムではプログラミング言語がそのために設計されたかのような見かけになる.そしてプログラミング言語とプログラムが相性良く落ち着いたとき,ユーザは明快で小規模で効率的なコードを手にする.ボトムアップデザインは,同じプログラムをただ別の順番で書くだけのことではないということは,強調しておきたい.ボトムアップでプログラムを作ると,出来上がるものが違ってくるのだ.単一の一体となったプログラムではなく,より抽象的なオペレータを持つ大規模なプログラミング言語と,それで書かれた小規模なプログラムが出来るのだ.ユーザは戸口に重くかぶさるまぐさ石ではなく,弧を描いて高く立つアーチを手にする.典型的なコードでは,瑣末で定型的な処理を一度抽象化すると,残るものはずっと短い.プログラミング言語を高く構築すればするほど,トップから降りてくる道のりは短くなる.これはいくつかの長所をもたらす:1. プログラミング言語に仕事を任せることで,ボトムアップデザインは小規模で機敏なプログラムを生む.短いプログラムは多数の構成要素に分割する必要がない.そして構成要素が少ないということは,プログラムが読みやすく修正しやすいものだということだ.また構成要素が少ないということは,構成要素同士の連結も少ないということなので,そこでエラーが起きる可能性も少ない.工業デザイナが機械の可動部分を減らそうと努力するのと同様に,熟練Lispプログラマはボトムアップデザインを使ってプログラムの大きさと複雑さを減らそうとする.2. ボトムアップデザインはコードの再利用を促進する.プログラムを2 つ以上書くとき,最初のプログラムのために書いたユーティリティの多くは他のプログラムでも有用になる.ユーティリティの大規模な基盤を手にしてしまえば,新しいプログラムを書く手間は,Lisp そのもので書かなければならないときにかかる手間の数分の一にすぎない.3. ボトムアップデザインはプログラムを読みやすくする.この種の抽象化に従ったものは,読む人に汎用オペレータを理解するよう要求する.それに対し機能の抽象化に従ったプログラムは,読む人に特定用途のサブルーチンを理解するよう要求する1.4. ユーザにコード内のパターンに常に関心を払うようにさせる.ボトムアップでの作業はプログラムのデザインに関するアイディアを明確にするのに役立つ.一つのプログラムの中で種類の違う2 個の構成要素が形の上で似ているなら,類似性に気づくよう促されるし,おそらく単純な方法でプログラムをデザインし直すよう促されるだろう.ある程度までは,ボトムアップデザインはLisp 以外のプログラミング言語でも可能だ.ライブラリ関数を見ればそこには必ずボトムアップデザインの例がある.しかしLispはこの点に関してずっと幅広い力を与え,プログラミング言語がそれに見合った大きな役割をLisp のスタイルで演じるよう促すその役割があまり大きいので,Lisp はただのプログラミング言語の一つではなく,全く違ったプログラミング方法になっている.このスタイルの開発は,小規模なグループによって書ける程度のプログラムに適しているというのは確かに真実だ.しかし同時に,このスタイルは小規模グループのなし得ることの限界を拡張する.Frederick Brooks が『人月の神話』(The Mythical Man-Month) の中で示唆したのは,プログラマのグループの生産性はその規模に対して線形には増大しないということだった.グループのサイズが増大するにつれ,個々のプログラマの生産性は低下してゆく.Lisp プログラミングの経験は,この法則のポジティブな捉え方を示唆している:グループのサイズが減少するにつれ,個々のプログラマの生産性は向上してゆくのだ.相対的に小規模なグループが勝利を収める.理由は単純,小さいから.小規模グループがLisp によって可能になるテクニックを活用するとき,完全な勝利が待っている.1 「でも君のユーティリティを全部理解しないことにはプログラムが読めなくなるじゃないか.」そういった言葉は大抵誤りだ.なぜかについては,4.8 節を参照.1.3拡張可能なソフトウェアLisp スタイルのプログラミングは,ソフトウェアが複雑さを増すにつれ重要性を増してきた.今時のユーザからのソフトウェアへの要求はあまりに多く,プログラマにはほとんど予想しきれない.ユーザ自身も要求のすべてを予想することができなくなっている.しかしユーザの望みを完璧に叶えるソフトウェアをプログラマが供給できなくとも,プログラマには拡張できるソフトウェアを供給することはできる.プログラマが自分のソフトウェアをただのプログラムからプログラミング言語に転換すれば,技術の高いユーザは必要な付加的機能をその上に作り上げられる.ボトムアップデザインは拡張可能なプログラムに自然につながっていく.ボトムアッププログラムの最も単純なものは,2 層から成る:プログラミング言語とプログラムだ.複雑なプログラムも一連の層として書き上げることができる.それぞれの層は1段下の層に対してプログラミング言語として機能する.この方針が最上層まで貫かれれば,その層がユーザの使うプログラミング言語となる.そのようなプログラムではあらゆるレベルで拡張が可能になっていて,伝統的なブラックボックスとして書かれ,後から拡張性を付け加えたシステムよりもはるかにいいプログラミング言語が出来上がることが多い.X Windows とTEX はこの原則に基づくプログラムの古い例だ.1980 年代には高性能のハードウェアによってLisp を自らの拡張言語とする新世代のプログラムが可能になった.その最初は有名なUnix のテキストエディタ,GNU Emacs だ.次はAutocad で,これはLispを拡張言語とする大規模商用製品としては初になる.1991年にはInterleaf の新バージョンがリリースされたが,それはLisp を拡張言語に使っていただけではなく,かなりの部分がLisp で実装されていた.Lisp は拡張可能なプログラムを書くためには素晴らしくいいプログラミング言語だ.それはLisp 自身が拡張可能なプログラムであるからだ.Lisp の拡張性をユーザにも渡すようなLisp プログラムを書けば,実質的には何もせずに拡張言語が出来たことになる.そしてLisp でLisp プログラムを拡張することと,伝統的なプログラミング言語でそれを行うこととの違いは,誰かに直接会うことと手紙でやりとりすることとの違いのようなものだ.外部プログラムへのアクセス手段を提供するだけの拡張方法しか用意していないプログラムでは,あらかじめ決められたチャンネルを通じて連絡し合う2 つのブラックボックスになるにすぎない.Lisp では,拡張機能は背後にあるプログラム全体に直接アクセスできる.ユーザにプログラムのあらゆる部分へのアクセスを与えなければならないと言っている訳ではない.ただアクセスを与えるか与えないかをあなたが選べるということだ.このレベルのアクセスがインタラクティブな環境と結び付くと,最高の拡張性が得られる.自分のプログラムの拡張機能の基礎として使えるようなプログラムは,どれもかなり大規模になりがちだ.おそらく規模が大きすぎて全体的なイメージが掴めないほどだろう.何か不確かな点があるときどうなるだろう? そのとき元のプログラムがLisp で書かれていれば,それをインタラクティブに調べることができる:データ構造を調査できる.関数を呼び出せる.ソースコードを読むことさえできる.この種のフィードバックにより,確かな自信を持ってプログラムを製作できる.思い切った拡張機能を書けるし,しかもそれが素早くできる.インタラクティブな環境は常にプログラミングを容易なものにしてくれるが,拡張機能を書いているときほどそれがありがたいときはない.拡張可能なプログラムは諸刃の剣だが,最近の経験によればユーザはただの剣より諸刃の剣のほうを好む.どんな危険性が備わっていようとも,拡張可能なプログラムのほうが勝るようだ.1.4 Lisp の拡張Lispに新しいオペレータを加えるには2 通りの方法がある:関数とマクロだ.Lispではユーザの定義した関数は組み込み関数と同じ地位を占める.もしmapcar の変種が新しく必要になったら,それを自分で定義し,mapcar を使うのと同じようにそれを使うことができる.例えば,ある関数が1から10 までの整数に適用されたときにそれが返す値のリストが必要なら,新しいリストを作ってそれをmapcar に渡すことができる:(mapcar fn

(do*

((x 1 (1+ x))

(result (list x) (push x result)))



((= x 10) (nreverse result))))

しかしこの方法は格好悪いし非効率だ2. 代わ

りに新しいmap 系関数map1-n(p. 55参照)を定義し,それを次のように呼び出

せばいい:

(map1-n fn 10)

関数を定義するのは比較的真っ当な方法だ.マクロは

新しいオペレータのさらに一般的な(しかしあまり理解されていない)定義

方法を提供する.マクロはプログラムを書くプログラムだ.この文には非常に深

い意味が込められている.そしてその探求はこの本の主目的の一つなのだ.

マク

ロを注意して使えば驚異的に明解でエレガントなプログラムができる.もちろ

ん,これらの宝石のようなプログラムがただで得られる訳ではない.最後にはマ

クロは世界で一番自然なものに思えるだろうが,最初は理解が難しい.これはマ

クロが関数よりも一般的で,書くときに注意すべきことが多いせいもある.しか

しマクロの理解が難しい大きな理由は,それが全く異質(foreign) なものだから

だ.Lisp のマクロのようなものを持つ言語は他にない.だからマクロを学ぶに

は,他のプログラミング言語から気づかずに学んだ先入観を振り捨てることを

要求されるかもしれない.そのなかの主要なものは,死後硬直に悩まされるプロ

グラムという概念だ.なぜデータ構造が流動的で変更可能であるべきなのに,プ

ログラムがそうであってはいけないのだろうか? Lisp ではプログラムがデータ

であるのだが,この事実の持つ意味をモノにするにはしばらく時間がかかる.

2 これは新しいCommon Lisp のシリーズマクロを使ってもっとエレガントに書くこ

ともできる.しかしこれらのマクロ自体がLisp の拡張なので,結局は同じこと

だ.

マクロに慣れるのにしばらく時間がかかっても,それは努力に見合うこと

だ.繰り返しのようなありふれた用途でも,マクロはプログラムを抜群に小型で

きれいなものに変える.ここで,あるプログラムがコード本体をx についてa か

らb まで繰り返さなければならないとしよう.Lisp 組み込みのdo はもっと

一般的な目的のためのものだ.それを単純な繰り返しに使うと一番読みやすい

コードにはならない:

(do ((x a (+ 1 x)))

((> x b))

(print x))

こ

れを,こう書けるとしたらどうだろうか:

(for (x a b)

(print x))

マクロがこ

れを可能にする.6 行のコードで(p. 159 参照)for

文をこのプログラミング

言語に追加できる.そしてそれは初めからあった構文のように機能する.後の章

で見るように,for

の実装はマクロでできることの手始めでしかない.

Lisp の

拡張で一度に使えるのは関数やマクロ1 個ずつに限られる訳ではない.必要とあ

ればあるプログラミング言語全体をLisp の上に構築し,それを使ってプログラ

ミングすることもできる.Lisp

はコンパイラやインタプリタを書くのに最適の

プログラミング言語だが,新しいプログラミング言語を定義する別の方法を提供

する.そのほうがしばしばエレガントだし,労力が少なく済むのは確かだ:それ

はLisp

を修正して新しいプログラミング言語を定義する方法だ.そうすれば

Lisp

の機能のうち新しいプログラミング言語でも変更せずに使える部分(例え

ば算術演算やI/O)はそのまま利用でき,異なっている部分(例えば制御構造)

だけ実装すればいい.このように実装されたプログラミング言語を埋め込み言語

と呼ぶ.

埋め込み言語はボトムアッププログラミングの自然な帰結だ.Common

Lisp には既にいくつか例がある.一番有名なCLOS については後の章で議論す

る.しかし自分だけの埋め込み言語を定義することもできる.自分のプログラム

に適した埋め込み言語を作ることができ,それをLisp とかけ離れたものにする

ことさえ可能だ.

1.5 なぜ(またはいつ)Lisp か

これらの新たな可能性は

魔法の要素たった1 個から生まれる訳ではない.この点を見れば,Lisp はアー

チのようなものだ.楔形の石(迫石?2)のうち,どれがアーチを支えているの

か? これは質問そのものが誤っている:どの石もアーチを支えているのだ.

アーチと同様,Lisp は組み合わさった機能の集合体だ.それらのいくつかをこ

こで挙げることができる動的メモリ割り当てとガーベジコレクション,実行時

型指定,オブジェクトとしての関数,リストを生成する組み込みパーサ,リスト

として表現されたプログラムを受け付けるコンパイラ,インタラクティブな環境

等々しかしどの一つをとってもLisp の持つ力の理由にはならない.Lisp プログ

ラミングをLisp プログラミングたらしめているのは,そのコンビネーション

だ.

過去20 年でプログラミングの方法は変化した.インタラクティブな

環境,動的リンク,オブジェクト指向プログラミングまでこれらの変化の多く

は,Lisp の柔軟性を幾分かでも他のプログラミング言語に与えようとする細切

れの試みだった.アーチの喩えは,それらがどれほど成功を収めたかを示唆して

いる.

Lisp とFortran が現存するプログラミング言語のうち最も古いものだと

いうことは,よく知られている.おそらくもっと意味のある事実は,それらはプ

ログラミング言語のデザインの思想のうち対極にあるものを代表しているという

ことだ.Fortranはアセンブリ言語からの進歩として開発された.Lisp はアルゴ

リズムを表現するプログラミング言語として開発された.そういった異なる意

図は,大きく異なるプログラミング言語を生んだ.Fortran はコンパイラ開発者

の人生を楽にしてくれる.Lispはプログラマの人生を楽にしてくれる.そ

れ以来,大抵のプログラミング言語は2 つの極の間のどこかに位置するものだっ

た.そしてFortran とLisp は真ん中に向かって歩み寄ってきた.Fortran は今

ではずいぶんAlgol に似てきたし,Lisp は若かりし頃の無駄な習慣をいくつ

か諦めた.

最初のFortran とLisp は一種の戦場のようなものを定義した.片方

では鬨の声は「効率~!(それに実装が大変すぎるだろうし)」で,もう片方で

は鬨の声は「抽象化~!(これは商用ソフトじゃないし)」だ.古代ギリシャ

の戦争の結果を神々が高みから決めたように,この戦の結果はハードウェアに

よって決められるようになっている.年を追うごとに情勢はLisp

側に有利に

なってきているようだ.今ではLisp に対する議論は,1970

年代初頭にアセンブ

リ言語プログラマたちが高水準プログラミング言語に対して仕掛けた議論に非常

に似てきた.今や問いはなぜLisp か? ではなくいつLisp か? になっている.

*2 訳注:voussoir: アーチや丸天井に使われる楔形の石材.