読者です 読者をやめる 読者になる 読者になる

八発白中

はてなブログに引越しました。

Roswell時代のCommon LispのWebアプリケーション運用

最近Quickdocs.orgフルスクラッチしました (2回目)。

今回のメインは裏側の月次バッチ処理でしたが、Webアプリ部分も多少変更をしました。特に運用方法が、Roswellを全面的に使うことで大きく変わりました。

Common LispのWebアプリ開発では、未だにこのブログの以下のエントリが参照されてるっぽいですが内容もかなり古くなっています。なのでこの機会に現代に合わせてアップデートしておこうと思います。

Roswell

何はともあれRoswellが必要です。

Roswellの紹介記事は以前書きました。

RoswellはCommon Lispスクリプトを簡単に実行できる機能があると共に、その配布も簡単にしています。

ros install <ライブラリ名>を実行すると、ライブラリのroswell/ディレクトリ以下のスクリプト~/.roswell/bin以下にコピーして利用可能にしてくれます。

使うライブラリ

QuickdocsではWebフレームワークにCaveman2を使っています。

Caveman2はClackに対応していてDBライブラリとの連携もある軽量なWebフレームワークです。

合わせてDBアクセスはdatafly、テンプレートエンジンはDjulaを使います。どちらもCaveman2のデフォルトです。

アプリケーション起動

開発時はREPLで (quickdocs-server:start) を実行すればHTTPサーバが起動しますが、デプロイすることを考えるとシェルから実行できる必要があります。

Clackはシェルコマンドの「clackup」を提供しているのでRoswell経由でインストールします。

$ ros install clack

Webアプリのプロジェクトルートにapp.lispというファイルがあるので、それをclackupコマンドに渡せばHTTPサーバが起動します。

$ clackup app.lisp
$ clackup --server :woo --port 38080 --debug nil app.lisp

ちなみに以前のQuickdocsはFastCGIを使っていましたが、今はWooで動いています。

ライブラリのバージョン固定

Webアプリケーションを長く運用していると、最新のライブラリではいつの間にか動かなくなっている、というようなことがあります。うまく動き続けている間は問題がありませんが、サーバの追加や移転をしようとしたりするときに困ります。

また、他の誰かがアプリケーションを動かすときに、自分と同じライブラリバージョンを使わないと挙動に齟齬が生じます。

Qlotを使えば、使うライブラリのバージョンを固定できるのでこれらの問題は解決します。clackupと同様、Roswell経由でqlotコマンドをインストールします。

$ ros install qlot

プロジェクトルートにqlfile を作って、qlot installをするとプロジェクトローカルにQuicklispがインストールされ、qlfile.lockができます。バージョンの固定にはこのqlfile.lockが必要なので、qlfileと合わせてコミットしておきます。

プロジェクトローカルのQuicklispを使うにはqlot execします。

$ qlot exec clackup app.lisp --debug nil --server :woo --port 38080

ホットデプロイ

デプロイ時に既存のプロセスを殺して新しいプロセスを立ち上げると、どうしてもサービスのダウンタイムが発生します。

これを防ぐためにServer::Starterを使ってclackupを実行します。これは新しいプロセスをforkしてHTTPサーバを立ち上げるのでportの競合なくファイルディスクリプタを新旧プロセスで共有し、新プロセスが正常に立ち上がったら旧プロセスを殺します。

clackupはServer::Starterに対応しているので使うのに特に難しいことはありません。

$ start_server --port 38080 -- qlot exec clackup app.lisp --debug nil --server :woo

死活監視

アプリケーションプロセスが、何らかの要因でいつの間にか落ちてサービス停止するのを防ぐためにプロセスの死活監視を行います。これにはSupervisorを使います。

リバースプロキシ

アプリケーションサーバの前段にNginxをリバースプロキシとして置きます。ここではアクセスログの出力や、エラー発生時にエラーページを返したり、その他静的ファイルの配信を行います。

デプロイ

デプロイ時に毎回SSHしてgit pullして再起動するのは面倒なので、ローカルから簡単にデプロイできるようにします。これにはFabricを使います。

こんな感じのfabfile.pyを書いておけば以下のようにデプロイができます。

$ fab server deploy

デプロイ時には以下のことをしています。

  • git pullする
  • qlot installする
  • supervisord からプロセスを再起動する

supervisorctl restartするとSupervisorがServer::StarterにSIGTERMを送って、Server::Starterが新しいプロセスを立ち上げます。立ち上がったら、Server::Starterがclackupにシグナルを送り、最終的にWooがリクエストを捌いてから終了します。

ただし、このfabfile.pyには実は問題があります。

git pullした時点でHTMLテンプレートや静的ファイルは更新されて再起動なしで配信されてしまうので、走っているプロセスとの不整合が起きます。

これを防ぐにはデプロイ時に毎回別ディレクトリにgit cloneして、サーバ起動前にすり替えるみたいなことが必要なんですが、Fabricはそういう気の利いたことをしてくれないので自分でPythonを書く必要があって面倒なので今のところ放置しています。

サービス監視

サービス監視にMackerelを使っています。

ただ、Quickdocsはまだそれほどトラフィックもないので、DBもアプリもバッチもリバースプロキシも1台のサーバに共存しているので、全く活かせている気がしませんが…。

f:id:nitro_idiot:20150804154702p:plain

まとめ

全体的にCommon Lispもこなれてきたなーと思います (毎回言ってる)。Roswellのおかげで簡単にスクリプトが書けるようになったのでServer::StarterやSupervisorなど、他のツールとの組み合わせが簡単になっています。

心残りはFabricかなー。もうちょっと何とかなりそうだけどPythonは書きたくないし。Common LispのデプロイツールがあればリモートのSwankサーバとの連携とかもできそう。

今回紹介したQuickdocsのWebアプリ部分はGitHubに公開されています。

Ansibleのplaybookはこちらです。

訳本「ヘルシープログラマ」を読む

以前、「The Healthy Programmer」を読んだ話を書きました。この夏にその邦訳の「ヘルシープログラマ」が出版され、僕のブログ記事を見ていただいた訳者の玉川竜司さんに献本いただいたので、今もう一度日本語で読み返しています。

f:id:nitro_idiot:20150802204203j:plain:w320

内容としては、普段座りっぱなしのプログラマ向けに健康を考えましょう、という主旨の本です。

プログラマは生産性を上げることが仕事の一部なので、普段から仕事の障害と思うものに対してセンシティブな方だと思います。

ずっとイスに座ってプログラム書いてると腰が痛くなって生産性が下がる。そのとき腰の苦痛を解消するために10万のイスを買ったりとか対症療法的な解決をしてしまいがちです。けど、そういう手段には当然ながら限界がありますよね。

かと言って今度は漠然と、運動しよう!とかいってフルマラソン走ったりする人もいるけど、それで逆に体を壊しても意味がないです。

そこでヘルシープログラマでは、もっと長期的な視点で無理なく健康を維持することで生産性を落ちないようにしようという提案をしています。特に、あまり「運動」に含まれそうにない「散歩」が生産性の維持に与える効果を説明してとてもおすすめしています。

と言っても、振り返ってみて実際に自分の生活に活かせているかというと難しい…。

f:id:nitro_idiot:20150730210703j:plain:w360

活かせている点は、以前のブログ記事を書いたときにエアロバイクを買って机の隣に設置したので、ちょっとしたスキマ時間に乗ったりしています。git pushしてTravis CIが回ってるのを待つ間に画面見ながら乗ってみたりとか。

一方で全く活かせてないところは、家の外に出る機会がほとんど無くなっていることです。それどころか、集中力を持続させるために部屋の移動すら少なくしようと、机の周りで何でも済ませられるようにしてみたり。最近はお茶を汲むためのインタラプションを少しでも軽減するために電気ポットを移動したり、安易に対症療法的な解決をしてしまっています。もっと歩いたほうが血の巡りも良くなって頭の働きも良くなるかもしれないのに。

引越しをしてから、以前は良くしていた散歩も全然しなくなっています。家の環境を良くしすぎなのと、家が大通り沿いであんまり外に出たくないなーっていう心理的な障壁が問題な気がしています。

本を頂いたおかげで良い振り返りの機会を得られたと思います。こういうとき、「ヘルシープログラマ」自体が全然説教臭くないので、変にひねくれずに素直にまた改善しようという気持ちになれるのも良いですね。

近くにいくつか公園もあることだし、これを機会にせめて一日20分程度の散歩を日常に取り入れたいものです。

ヘルシープログラマ ―プログラミングを楽しく続けるための健康Hack

ヘルシープログラマ ―プログラミングを楽しく続けるための健康Hack

Common Lispのコミュニティ事情

初対面の人と二人きりで話すという機会はあまりない。

「普段どんなお仕事をされてるのですか」

──もし訊いても良ければ。そう遠慮がちに言い添えた彼は、コタツを挟んで斜め向かいから好奇心旺盛な目でこちらを見ていた。

プログラマですよ」

そう答えてから、それが十分な情報にならないことに気づいて、「Webアプリケーションとかを作ります」と慌てて付け加えた。とはいえ、それでも大した情報量ではない。

彼はそれを聞いて感心したような表情を作ってからもまだ何か僕の言葉を待っているような様子だったが、僕がそれ以上は口にしないのを感じ取ると言い訳するように言った。

「いや、平日に急に三連泊なんて珍しくて」

その日唐突に山口県萩市を訪れることにしたのに大した理由があったわけでもない。この町は吉田松陰高杉晋作のふるさととして知られる。今も古い城下の残るちょっとした観光地だが、日本海側に面しており新幹線の停車駅からも空港からのアクセスにも恵まれず、訪れる人はそれほど多くない。いつか行こう行こうと思ってなかなか機会を得なかった土地だ。心が決まったのがたまたまこの三月だったというだけだった。普段から仕事は自宅でしているし、自宅に限らずどこにいてもリモート勤務ができる。数日の間、萩でする仕事も悪くあるまい。

「普段から自宅で仕事していますからね。実際どこにいたっていいんです」

東京のIT企業でもまだ浸透しているとは言えないリモート勤務について、この観光地でゲストハウスのオーナーをしている彼に理解が及ぶかという不安もありつつありのままを言ってみた。しかし、意外にも彼は納得したようだ。

「知人にもそういう人が何人かいます。プログラマでね。物を作ってメールで送ればいいんだそうです」

彼にはさまざまな「知人」がいるようだ。考えてみれば自然なことだ。ゲストハウスは多くの人が訪れて一夜して去っていく。宿泊客同士はなんらかの偶然で一時を共有する。多くの人の体験や思想が集って拡散していくハブとしての役割をゲストハウスはしており、各地に広範なネットワークを作っている。彼自身も旅好きのようで、冬の休みに観光に行く際は各地のゲストハウスを訪れて情報を得たりしているらしい。自分には全く馴染みのない世界である。

その日は僕の他に宿泊客はいなかった。障子を通して中庭の柔らかい光が居間に広がっている。平日の昼にゲストハウスに人がいる、というだけで珍しいのかもしれない。彼は視線を手元のPCに移しながら尚もひとりごとのように言った。

「それなら、ほとんど人と会う必要もないでしょう」

彼の意図するところがわからなかったが、僕はそれに答えて言った。「まあ、たまに決まった人とは会いますけれどね」

決まった人。顔が浮かぶのはよく会うCommon Lisperたちだった。「たとえばこの前の週末には友人を僕の家に呼んで、話をしたりお互いのプログラムについて意見を言い合ったりしました」

ほう。彼は顔を上げて意外そうに言った。「プログラマもそのように一箇所に集まったりするんですね」

プログラマといえば日がなコンピュータに向き合って、人にはあまり興味がないというステレオタイプが彼の頭にあるのかもしれない。もしくはすべてをWebで完結すればいいという先進的な人種だと思っているのだろうか。いずれにしても、正解からは遠い。僕は彼の方に向き直り、ゆっくりと言葉を選びながら言った。

プログラマというのは世間で思われている印象とは違って、存外に社会的な生き物なんですよ」

僕は周りで日常的に為されているオープンソース活動について話をした。プログラムを書くときは大抵一人だ。けれど、作ったプログラムを他人にも使ってもらいたいと思う。それを公開する。そこで賞賛や批判を得る。そうすると、それを一緒に改善しようという動きが出てくる。そういった独特の社会性が、プログラマの間では構築されている。

どれだけ伝わったかはわからない。けれど、彼はそれを静かに、ところどころで相槌を打ちながら聞いていた。

「積極的な人だと週末などに勉強会のようなものを開いて、新しい技術を一緒に学ぼうという活動もありますよ」

もちろん、個人的にはむやみに人に会うことは好きではありませんがね、と僕は少し笑って見せた。

今思えば彼にとって不思議だったのは、仕事では人に会わないという選択を良しとしておきながら、週末にはわざわざ人に会おうという二面性にあったのだと思う。だとすればその疑問には全く答えられていないわけだが、彼もそれ以上詳しく訊こうとはしなかったのでうやむやのままである。

Shibuya.lisp

とはいえ、ここ数年はあまり積極的にいろんな勉強会に参加することはしなくなった。唯一渋谷のLispコミュニティ「Shibuya.lisp」にだけはできるだけ参加し、まとまったネタがあれば発表するようにしている。

Shibuya.lispPerlコミュニティの「Shibuya.pm」にインスパイアされてhigeponさんやg000001さん、naoya_tさんを中心に作られたLispコミュニティだ。Common Lispに限らずLisp方言を全般に扱う。

当初は「Technical Talk」という、聴衆を多く集めてLispに関する発表を皆で聴くというスタイルのイベントを数ヶ月置きに開催していたのだが、最近では毎月開催の「Meetup」という小規模なものをメインに運営されている。

「Meetup」は毎月一度、平日の夜に渋谷のサイバーエージェントの会議室を借りて開催される。参加者と発表者はWebで募集され、毎回二〇人ほどが参加する。独特なのは月替りでテーマとなるLisp方言が変わるという趣向だ。Common LispSchemeClojureが順繰りにしばらく回っていたのだが、今年一月を最後にSchemeの参加者不足によりScheme回はスキップされており、今はCommon LispClojureが交互にテーマに選ばれている。

小規模ながらUstreamでの同時配信と、開催後の録画の公開も行っており、この点は非常に意欲的だ。これから参加しようか迷っている人はまずはShibuya.lispのWebサイトを見てみることをお勧めする。

知らない人も多いかもしれないが、実はShibuya.lispの運営は数年前にすっかり入れ替わっている。Technical Talkの定期開催も途絶えて二年ほど経った頃、g000001さんの提案により運営の見直しがなされたときに運営を担っていた人々は解散した。それと同時に、Shibuya.lispの継続を望む人々が手を挙げ、新たに第2期Shibuya.lisp運営チームが組織された。今Shibuya.lispを運営しているのはその人たちである。

新しい運営の最初の仕事はコミュニティの運営方針を考えることだった。今までの運営チームと同じである必要はないし、メーリングリストには新しいメンバーでできそうな意欲的なアイデアがいくつか挙げられていた。が、当然ながらそれらの方向性は、考えたメンバーによってバラバラだった。

メーリングリストで手を挙げただけの、互いに顔を合わせたこともない人々で話をまとめるのは難しい。特に、これからの運営どうしましょうかね、といった曖昧な内容をメールのやり取りだけでまとめるのは至難だろう。そこでまずは、新運営陣の顔合わせも兼ねて渋谷で対面のブートストラップミーティングを開くことになった。そのときは僕も新運営の一人として参加していた。

そのミーティングの席で僕はこんな発言をしたのを覚えている。

「そもそもShibuya.lispが何を目的として存在するのかを考えなくちゃいけないんじゃないですか」

単にイベントを開催するにしても、なぜ、というのが明確でなければいずれまた目的を見失う。Shibuya.lispは何かの手段であって、目的ではないんじゃないか、と。

僕のその言葉に一同黙り込んだ。さあ、Shibuya.lispをやる目的とは何だろうか──。

そのとき前運営の立場として参加していた佐野さんが言ったことは、図らずも新しい運営方針を決定付けたものかもしれない。

──「理由はそのコミュニティに集まった人々で作ればいいことで、我々はただその箱を用意すればいいんじゃないかな。だとすれば、『続ける』ということも十分目的になるよ」

そうして、今はTakehiko Nawataさん、Katsunori Kanda (potix2) さん、κeenさんを中心に「Lisp Meetup」が二年半の間、毎月続いている。東京のLispコミュニティは彼らに支えられている。

ELS 2015 @ London, UK

話は変わって、今年の四月にロンドンで「ELS (European Lisp Symposium)」が二日間に渡って開催された。僕は登壇者の一人として初参加、佐野さんは常連参加者の一人として日本から参加した。

ELSはヨーロッパのLisp方言全般のコミュニティだ。一年に一度、大学の講堂やホテルの一室を借りてイベントを開催し、ヨーロッパのさまざまな国から人々が集まる。共通言語は英語だ。

世界規模のイベントと言えば、数年置き開催の「ILC (International Lisp Conference)」というものもある。こちらは北米での開催が多いが、「International」と冠していることからも分かる通り、特に開催大陸が決まったものではない。二〇〇七年はケンブリッジで、二〇一二年には京都でも開催された。

日本からの参加者として見ればどちらも遠い「海外カンファレンス」ではあるけれど、ヨーロッパのコミュニティはILCとは参加者の顔ぶれや雰囲気は異なる。ELSはILCよりも若い参加者が多く、発表もアカデミックなものより実務寄りのものが目立つ。参加者もELSのほうがいくらか多いように見えるが、これはチケット代やビザの取得のしやすさの違いもあるかもしれない。

余談だが、以前は「ECLM (European Common Lisp Meeting)」というCommon Lisp限定のヨーロッパコミュニティもあった。しかし、種々の事情によりここ二年開催されなくなっており、こちらのイベントには僕は一度も参加したことはない。運営を担っていた人が抜けたこともあって、今後の開催も期待はできない。

海外の技術イベントに参加するときに興味深く思うのは、トークの間にある「休憩 (Coffee Break)」だ。二時間置きに三十分の休憩があるという具合で、その時間は皆席を立って思い思いの人と話をする。廊下に出るとコーヒーポットやクッキーが並ぶ。この休憩は文字通り休憩という意味もあるが、交流という意味が強い。登壇者に聞きたいことがある場合はこのタイミングでつかまえる。

https://twitter.com/meymao/status/590459858199130112/photo/1

一日目の最初はQuicklispの作者として知られる「Zach Beane」のKeynoteスピーチで始まる。「Quicklisp: On Beyond Beta」というタイトルで、Quicklispの概要と、プロジェクトのこれからについての話だった。

そのトークの中で意外にも彼は日本のコミュニティについて言及した。

日本語圏のCommon Lispコミュニティは本当に盛況のようで、Clack、qlot、fast-http、optimaなど多くの素晴らしいプロジェクトが見られる。 There also seems to be a real boom in the Japanese-speaking Common Lisp community, and I see some of the effects with great projects like Clack, qlot, fast-http, optima, and many others.

── els-london-2015/script.txt

敢えて彼が特別に「日本」について言及したのは、北米に住む彼にとっても、またおそらくヨーロッパから集まった聴衆にとっても、「日本」という国の人々の活動が意外で馴染みのない場所からのコントリビューションだと見えるからかもしれない。

その後の休憩で、佐野さんと共に、Zachと話す機会を得た。と言っても、会話はほとんど佐野さんに任せて、僕は隣に立っていただけなのだけど。

話は日本のCommon Lispコミュニティについてだ。彼は自身のトークで日本のコミュニティは「盛況」だと言及した。ヨーロッパに招待スピーカーとして登壇するCommon Lisperの彼にとって日本のコミュニティはどのように見えているのか。

──日本語が読めないからよくは分からない。ただ、日本人が作ったCommon Lispライブラリを多く見るから。

Tokyoという場所

日本のCommon Lispコミュニティについて考えると、Common Lispプロダクトを作っている日本人の多くは東京近郊に集中しており、Lisp Meetupで作者本人と出会うことも多い。これは東京に人口や企業の多くが集中しているという日本の地理的事情が無関係ではないだろう。

数年前に京都に住んでいたときに「Kyoto.lisp」というコミュニティを立ち上げようとした。しかし、京都の場合は近郊に人材が足りなさすぎた。初回は良かったが、二回目からはぐんと参加者の数も減った。最後には参加者も発表者も、兵庫や愛知、東京からの参加者を入れてようやく一〇人足らずという有り様だった。あるときその数少ない参加者の大学生から、東京の企業に就職が決まったので四月から参加できません、と聞いたときには東京という土地の引力に眩暈がしたものだ。その後コミュニティは運営に携われる人も見つけられず、数度のイベントを開いたきり続かずに終わってしまった。

GitHubで開発し、メールやTwitterでやり取りができるとはいえ、実際に会って話すことによるメリットはまだ確実にある。お互いに情報を交換し、問題意識を共有し、家に帰って形にする。このサイクルが東京のコミュニティではうまく回り、結果として海外カンファレンスで言及されるほどの存在感を得ているのだと思う。

僕は東京を住みやすい街ではないと思うけれど、人に会うということに関してはこの街ほど魅力的な場所はない。コミュニティの存在に感謝しつつ、今日も一人プログラムを書く。


この記事の下書きを読んで意見をくれた友人のMasatoshi Sanoと妻の@meymaoに感謝します。

コマンドラインからHyperSpecを開くRoswellスクリプト

コマンドラインからHyperSpecを簡単に開けるRoswellスクリプトを作ったので紹介します。

Roswellが何か知らない人はまずこちらの記事をお読みください。

blog.8arrow.org

昔はEmacsからemacs-w3mで開くことでEmacsから出ずに見られるように設定していたのですが、慣習として定着しなかったので最近はこのスクリプトでターミナルから開いています。

これならGUIのデフォルトブラウザで開けるので、Slackで質問されたときとかに「HyperSpec見ろ」と言ってURLを投げつけたりできて便利です。

インストール方法

Roswellをインストールしてください。

その後、上述のgistページからダウンロードして実行属性をつけ、PATHの通ったところに置くだけです。

$ wget https://gist.githubusercontent.com/fukamachi/3510ea1609c1b52830c2/raw/clhs.ros -O clhs
$ chmod u+x clhs
$ mv clhs /usr/local/bin

起動時間が気になるようなら、ros buildでexecutableを生成してしまえば待ち時間がほとんどなくなるのでおすすめです。

$ ros build clhs.ros
$ mv clhs /usr/local/bin

Roswellスクリプトにすると、こうして簡単に実行ファイルを吐けるので便利です。

使い方

clhsコマンドにシンボル名を与えるだけです。シンボルが見つかればデフォルトのブラウザでページを開きます。

$ clhs handler-bind
Opening "http://www.lispworks.com/documentation/HyperSpec/Body/m_handle.htm"

仕組み

初回にHyperSpecのシンボルリストのページを取ってきてパースしています。パースしたものは~/.cache以下に保存しており、次回以降はそのキャッシュを参照します。

キャッシュを作るために依存ライブラリとしてDrakma、Plump、CLSSが必要なのですが、一度キャッシュを作ってしまえば毎回ロードするのは無駄なので、キャッシュがないときだけこれら依存ライブラリがロードされます。この仕組みのためにQlotで使っているマクロwith-package-functionsをコピってきていて、その部分だけ少し技巧的です *1

Drakmaのリクエストが200以外を返したときは地味にデバッガからリトライができる、とかも対応しています。Common Lispのrestartの簡単な使い方として参考になるかもしれません。

真似して欲しくないところとしては、terminateにチルダを含むメッセージを渡すことが考えられてないことですが、スクリプト内で使うだけなのでわざわざエスケープ処理はしていません。

ソースコードは以下に貼り付けておきます。ライセンスはMITです。どうぞご利用ください。

*1:このマクロが必要な理由は、Common Lispのリード時エラーを防ぐためです。たとえばDrakmaがロードされていない環境で、シンボル drakma:http-request を含む式をリードしようとすると、Common Lispのリーダがパッケージを探しにいってエラーを吐きます。

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の場合プログラムはリストというポピュラーなデータ構造で扱える。これはコンパイル時にもそのプログラムに対して標準のリスト処理関数が存分に使えるという利点がある。