八発白中

技術ブログ、改め雑記

Lisperはプログラムに何を見るか

男子校に通う中学生の僕らにとって「家庭科」の授業は休憩時間のようなものだった。

僕の中学校には家庭科室というものがない。だから、いつもの教室で野菜の種類やそれに含まれる栄養素なんかを教わるというだけの、正直退屈な授業だった。話される内容はどれもただ暗記すればいいものなので、授業を聴かなくても定期試験前に教科書を読み通すだけで九〇点は取れる教科だった。

学校としても文科省の教育課程に沿うがためだけに時間割にねじ込んでいるに過ぎなかったと思う。特別教室がないことでも真面目にこの教科を取り扱う気がないことがわかるし、生徒の方でもその学校の態度を敏感に感じとっていた。

そんなやる気のない男子学生の前に立って話すのは教師にとって楽しいものではなかっただろう。僕らの先生は、落ち着いた雰囲気でどこかしたたかさのある、髪の長い女の先生だった。

その日も彼女はいつも通り、キノコに含まれる何々という栄養素が、体内でビタミンDになるという話をしていた。ふてぶてしくも一人のクラスメイトが先生に訊ねた。「こんなこと覚えて意味あるんですか」

すると先生は一瞬黙り込んでから視線を手元の教科書に落としたままひとりごとのように答えた。「意味のない知識が多いほど、人生は面白いの」

先生はそれ以上は言葉を加えず、再び何々という栄養素に話を戻した。

最初、僕は先生の言葉の意味を単純に知識欲を満たすことができるからだと思った。けれど、彼女が本当に言いたかったことは、もっと深い。知っているのと知らないのとでは、ものの感じ方が違う。同じものを見ていても見ているものが違うんだ。

知っている花が多ければ散歩中に見つけた花で季節を感じることができる。歴史を知らない人にとって関ヶ原はただの荒野でしかないけれど、知識があればそこに武者たちの壮大な物語も見ることができる。

彼女の授業で今でも覚えていることと言えば、レモンが評判ほどにはビタミンCを含まないという程度のことだけど、その日の彼女のその言葉だけは今でも鮮明に覚えている。

自分にしか見えないもの

逆に、自分には見えていて他人には見えていないものもある。

最近、妻がプログラミングの勉強を始めた。彼女は都内のWeb企業に勤めてはいるがプログラマではなく、仕事上求められるわけではない。ただ単純にプログラミングはどういったものか、何ができるのか、ということを知りたいという純粋な好奇心から学ぶことを始めたようだ。

最初に学ぶプログラミング言語は何がいいかな、と彼女は訊いた。僕は、JavaScriptが良いだろうね、と答えた。特別難しい言語ではないし、ブラウザという身近な処理系がある。何より学べば何かしら役にも立つかもしれない。

けれど、入門書という面ではこの言語は恵まれない。僕自身、良いと思ったJavaScriptの本は2冊しかない。そのうちの一つの「JavaScript 第5版」が家にあったので妻に与えてみたが、ほんの数章で読むのをやめてしまった。

それから僕と彼女は池袋のジュンク堂に行って、技術書コーナーを行ったり来たりすることになった。さまざまな言語の入門書をめくってみて彼女に最適なものを一緒に探した。

ある本は難しすぎ、ある本は内容が浅すぎた。これは、と思う本を手にとって彼女に意見を求めてみても、内容が難しい、と言われる。良い本かどうかの判断はできるが、何を難しいと思うのかは僕にはわからない。

最終的に選んだのは「初めてのPerl 第6版」だった。この本は僕が最初にプログラムを学んだ本でもあり、文章も面白くて読み進められる工夫がされている。他の言語を後に学ぶにしても、最初はPerlで入門してからなら理解も深まるかもしれない。

プログラミング学習の難しさ

僕のことを知る人は、僕の妻がCommon LispではなくPerlを学んでいると聞いて不思議に思うようだ。

自分の妻にCommon Lispを勧めない理由の一つは、彼女がその恩恵を受けるほど複雑な問題を解いたり、ある程度の規模のプログラムを書くという機会はないだろうという推測からだが、他にもCommon Lispを学ぶのは他の言語に比べて全く簡単ではないだろうという考えからでもある。

Lispは構文上のルールが少ないので、学ぶのが簡単だと言う人もいる。僕自身も少し前までそう考えていた。けれど、学習の容易さは覚えるべき文法ルールの多少によるものじゃない。プログラミング言語の習得の難しさは、その言語が提供する抽象化の概念の理解の難しさに比例する。

プログラミング言語はもともとコンピュータを抽象化する目的で作られた。人間が0と1の組み合わせでプログラムを書かなくてもいいようにアセンブリ言語ができたわけだし、さらに高等な命令を持つCのような言語も流行した。プログラミング言語の進化は概ね、より人間に優しい言葉になる進化だ。

その後、プログラミング言語はもっと高度な抽象化によってプログラムを簡単に書けるように進化した。それによって決して単純ではなくなるが、一度学ぶと簡単にプログラムを書けるようになるといった類の改善だ。オブジェクト指向がその一例だろう。Cがアセンブリ言語に対して行う抽象化とは違い、オブジェクト指向は非連続な言語の進化だ。オブジェクト指向の概念は、明らかにCのfor文より高度な抽象化だ。

進歩したプログラミング言語ほど、このような抽象化された概念が多い。――レキシカルクロージャ再帰関数、Cのポインタ、Perlのコンテキスト、Schemeの継続。そして、高度な概念を扱う言語ほど学ぶのが難しい。

その中でもLispは学ぶのが難しい部類だと僕は思う。事実、プログラミングの全くの初心者だけでなく、他の言語で十分な経験がある人でもLispを前に容易に降参するのを見た。僕自身、Schemeを学ぶ段階でPerlPHPJavaScriptの経験があったけれど、他の言語を習得するのに比べて多くの苦痛が伴った。Lispを学ぶことを困難にしているものは何だろうか?

この問いは、本にかじりついて学ぶ過程をくぐり抜けた僕にとっては無用ではあるけれど、今後Lispを学ぼうという人が挫折するだろう石ころを事前に取り除けるというならそれに越したことはない。何しろ僕には、いずれ妻にCommon Lispを教えなければならない日が来るかもしれないのだから、今のうちに考えておきたい。

Lispは今まで多くの言語に影響を与えてきたため、Lispが持つ多くの概念は他の言語の習得者にとって既に真新しいものではなくなっているはずだ。代表格と言える無名関数はもはやほぼすべての高級言語に含まれている。けれど、他の言語が真似できない、最もLispの中で重要な概念をそのカッコに隠している。Lispではそれを「マクロ」と呼ぶ。

マクロ: プログラム可能なプログラム

Lisp is a #1=(programmable . #1#) programming language.

マクロはプログラムを書くプログラムである。

人によってはこれを聞いただけで恐怖心を感じて考えるのをやめてしまう。そんなものいつ必要になるんだ? ――ほとんどいつでも。ただ、ここではその議論に立ち入るのをやめよう。深呼吸して、ただここではLispにそういった機構があるという事実だけを知っていればいい。

通常のプログラミング言語では、実行されるコードをプログラムに記述する。これを一層目のプログラムだとしよう。Lispではさらにその一層目のプログラムを生成する二層目のプログラムを書くことができる。このようなプログラムはメタプログラムと呼ばれる。

Lispが異常な点は、そのメタプログラムをユーザが自由に書けるところだ。メタプログラム内ではフルのLispの機能が使える。もちろん自分で定義した関数を使うこともできるし、ライブラリも使える*1。こういうプログラムが欲しい、と書けば、書くべきプログラムをLispが代わりに書いてくれる。

そしてこの層の連鎖は無限に続けることができる。つまり、プログラムを書くプログラムを書くプログラムを書くプログラムを書くこともできる*2Lispはプログラム可能なプログラミング言語なのだ。

なぜLispプログラマにとってマクロはそれほど重要なのか? それは単にマクロが提供する文法上の便利さなどではなく、マクロがプログラムを操作するプログラムであり、それがLispコミュニティの中心的テーマであり続けているからだ。FORTRANが数を、Cが文字とポインタをこき使う言語ならば、Lispはプログラムをこき使う言語なのだ。

Why are macros so important to Lisp programmers? Not merely for the syntactic convenience they provide, but because they are programs that manipulate programs, which has always been a central theme in the Lisp community. If FORTRAN is the language that pushes numbers around, and C is the language that pushes characters and pointers around, then Lisp is the language that pushes programs around.

Quoted from 「The Evolution of Lisp」by Guy L. Steele Jr. & Richard P. Gabriel

マクロを持つために必要なもの

何も僕は、Lispを学ぶならまずはマクロを学ばないといけないなんてひねくれたことを考えているわけじゃない。僕が思うのは、Lispにはマクロがあるからこそ他の言語にはない高度な抽象化を行っているために、それがLispを理解する妨げになっているんじゃないか、ということだ。

Lisp はより妥協の少ない哲学をもっていて、非常に強力でよく統制のとれた 言語の核を提供している。このことは Lisp を学ぶのをむずかしくしている。 なぜなら最初から高水準の抽象化を扱わければならず、見た目や感覚に 頼らずに自分がいま何をしているかを理解しなければならないからだ。

Quoted from「LispプログラマのためのPython入門」by Peter Norvig

じゃあ、Lispが提供する高度な抽象化とは何か。

一般に「Lisp」と呼ばれている言語の特徴は「プログラムをその言語自身の平易なデータ構造で表すことができる」ことだ*3

この特徴は「マクロ」と無関係ではない。プログラムを生成するプログラムを書くためには、プログラム自身がプログラムから扱いやすいものでなければならないからだ。

Lisp方言の一つであるCommon LispのコードはCommon Lisp自身の平易なデータ構造で表せる。式は「リスト」で表されるし、変数は「シンボル」というデータ型で表される。他の言語で変数を表す「変数型」のようなものがないことを考えても、やはりLispは特異だと言える。

自然、Lispプログラムには、「プログラミング言語としてのLisp」と、「メタプログラミング言語としてのLisp」が混在することになる。プログラマは、プログラム中のそれぞれの部位がどのタイミングで評価 (evaluation) されるかを意識しながらプログラムを書く。この制御にはクォートを使う。これも、マクロを実現するためのLisp特有の機能だ。評価する機能 (eval) はありふれていても、評価を抑制する機能がある言語が他にあるだろうか。

熟練のLisperたちは日常的に入れ子になった式の評価タイミングを意識して、息を吸うようにクォートを使いこなす。Lispを知らない人にとって平坦に見えるプログラムも、Lisperにとっては確かな立体感を持って知覚される。

おわりに

あの日家庭科の授業で生徒が正直過ぎる無礼さで質問をぶつけたとき、先生はこう言うこともできたはずだ。将来一人暮らしをしたときに自炊をしなければならないでしょう。そのとき自分の栄養管理をするのはあなた自身なのよ。けれど、そんな説教くさい話では生意気な男子中学生の態度を改めさせることなどできなかったに違いない。

Lispは学ぶべき言語だろうか。僕も説教くさい話はやめよう。Lispは楽しい。それを共感できる人間は、多いほうがいいけれど。


この記事の下書きを読んで意見をくれた同僚のRudolph MillerMasayuki Takagi@yanqirenshi、そして妻の@meymaoに感謝します。

*1:この利点を示すわかりやすい例として、プログラムを生成するときにハッシュテーブルを使って効率的なプログラムを生成するようなメタプログラムを書いたりできる。このアイデアは僕の書いた高速なHTTPパーサ「fast-http」で実際に使われている。

*2:これは必ずしも連続したプロセスである必要はない。Common Lispではコンパイル時に実行することもできるし、実行時にコンパイルすることもできる。

*3:最初期のevalを持たないLISPは完全なLisp実装ではない。現代の感覚で言えば最初期のLISPFORTRANのリスト操作ライブラリと言ったほうが正確なものだったろう。また、敢えて「平易な」と入れているのは誰かが「◯◯の言語でも構文木をデータとして扱うことができる」と言い出すのを見越してのことだ。Lispの場合プログラムはリストというポピュラーなデータ構造で扱える。これはコンパイル時にもそのプログラムに対して標準のリスト処理関数が存分に使えるという利点がある。