【LLTVレポート】劇的ビフォーアフター、匠の技（中編）

“本物のマクロ”でCのコード行数を半分に！

毎年夏に開催される軽量プログラミング言語（LL：Lightweight Language）をテーマにした「LLイベント」。第7回目となる「LLTV」が、2009年8月29日に東京・中野で開催された。この記事ではプログラムの一部、「大改善!!劇的ビフォーアフター」をレポートする。前編では、Rubyによるfortuneコマンドの“増築”と、Firefox拡張によるslコマンドの実装というネタ系発表をレポートした。中編となる本記事では、C言語にLisp風のマクロを取り入れ、lsコマンドのソースコードを約半分に“修繕”する匠の技をレポートする。後編では、売り場業務が滞りがちだった販売管理システムをbashコマンドで“建て直した”という劇的ビフォーアフターの発表をレポートする。

Cで書かれたlsのソースコードの問題点

匠の川合史朗氏

「Real/Macro Metaprogramming On C」と題した発表を行ったのは、Scheme処理系「Gauche」（ゴーシュ）の開発者として知られる川合史朗氏だ。アカデミックな情報科学の議論から、アセンブラレベルの最適化に至る泥臭いハッカーの実装話までカバーするスーパーハッカーとして知られる“匠”だ。

川合氏が改修を試みたのはlsコマンド（GNUのものではなく、FreeBSD付属のもの）。ソースコードは1141行。川合氏は「非常に素直で読みやすいコードで、C言語で書かれたソースコードとしては優等」としながらも、「LLを使い慣れてスポイルされてしまったプログラマからみると、どうしても不満点が見つかる」と、冗長性やメンテナンス性の悪さを指摘した。

例えばlsコマンドでファイルリストを表示するとき、昇順／降順でソートすることができる。ソートで比較するのはファイル名だったり、タイムスタンプだったりいろいろ。このとき関数ポインタで渡される比較関数として、1つの比較対象につき昇順と降順の2種類が、ペアでcmp.cに定義されているという。これらは関数名と動作が逆である以外はまったく同じ構造で、「新しいソート順を加えたいときに、いちいち2つ書かなきゃいけない」（川合氏）というムダがある。「LLの高階関数が使えれば、関数を引数にとって、その逆の動作をする関数をオンザフライで作れるのですが、C言語では、そういうことができない。とても残念です」（同氏）。類似した構造を持つパターンをくくり出すための抽象化機能が貧弱なC言語の限界、というわけだ。

川合氏が示した問題点の1つ。C言語の抽象化機能が貧弱であるために、似たような関数を2つ別々に定義してある

同様に、非本質的な情報がコードに入り込むのも、C言語のソースコードに見られる問題点だという。forループでカウンタを使って配列にアクセスするようなコードや、連結リストをポインタで順にたぐるようなループでは、抽象度の高い言語なら、より端的に意図したとおりの記述が可能。川合氏は「こう書ければなあ」と擬似コードを示すことで、C言語の抽象機能の弱さを指摘した。

ループの抽象化ができないのもCの弱さ

川合氏が指摘した3つ目の問題点は、コード中で情報の分散が起こりやすいこと。lsでは多数あるオプションをフラグで管理している。ところが、フラグに関連したコードは、ヘッダファイル、変数を宣言している場所、main関数のオプションパーザと3個所が関係しており、「1つのオプションを付け加えるのにも、最低3個所の改変が必要」と、抽象機能の貧弱さからメンテナンス性が悪い点を指摘した。

オプションスイッチに関するコードが、ヘッダ、変数宣言、オプションパーザの3個所に分散してしまう

本物のマクロで大幅リフォーム！

Cで書かれたlsのソースコード。そこにある「冗長性」、「非本質的な情報」、「情報の分散」という3つの問題点に対して繰り出された匠の技は、“本物のマクロ”だ。ここで川合氏がいう本物のマクロというのは、Cのプリプロセッサで処理されるテキスト置換のようなマクロではなく、Lisp風の「コンパイル前に、任意のソースコード変換ができるマクロ」だという。

「（プログラミング言語の）構文なんて飾りです、偉い人には分からないかもしれませんが、ここにいる皆さんには分かってもらえると思います」。リフォームのポイントをこう説明すると、川合氏はおもむろにC言語で書かれたコード片を次々と異なる構文に変形してみせた。関数呼び出しの例としてprint文、制御構造としてif文を、「これは、こう書いても同じことです」とカッコだらけのLispのS式に置き換えると、予想外（もしくは予想通り）の展開に会場からは笑いがわき起こった。

極め付けは、Cのmain関数のコード例だ。「例えば、こういうmain関数を、こう書くこともできます」と、ありふれたCのソースコードが、そこはかとなく原形をとどめながらも、誰も見たことがない不思議な字面のソースコードに変換されるのを見せつけられるに及んで、会場は匠の“荒技”に圧倒されて、大きな笑いに包まれた。匠は「見た目が、ちょっと変わるだけ。慣れればスラスラ読めます」と、こともなげだ。

川合氏が示したCの典型的なコード例

上のCのコードをCiSEと呼ぶ形式で書き直した例。S式でCを書いているという

魔法のようなコード変換

S式化されたCのソースコードに会場は笑いを誘われたが、これは“壮大なネタ”などではなく、川合氏が「CiSE」（C In S-Expression）と名付けたまじめな取り組みだ。CiSEによって、CのABIやライブラリ、ハードウェアに近いセマンティクスなど好ましい特徴を変えることなく、抽象構文木を操作してコードを生成するようなマクロを使うことができる。実用性を重視したというScheme処理系のGaucheの実装でも、一部にCiSEを使っているという。

マクロの威力を示すものとして川合氏が示した最初の例は、forループでカウンタを0からインクリメントして配列要素にアクセスするパターンの抽象化。文字列の長さに応じてforループを回すような処理では、「dotimes」と名付けたマクロによって、非本質的な一時変数はコードから消失し、カウンタを0で初期化するという低レベルの処理も隠蔽できることを示した。CiSEでは、一時的に必要となるC上の変数などは自動的に通し番号で管理され、名前の衝突が起こらないよう配慮されているという。

マクロを使ってループを抽象化した例。一番上が元になるコード。真ん中がそれをマクロで展開した表現で、それをC言語に変換するといちばん下のコードが生成できる

このほかマクロを使うことで、繰り返しパターンの除去や、本質的な処理のくくりだしによる冗長なコードの整理、DSLのように宣言的構文で複数ファイル上の該当個所にコードを生成して、分散していた情報を1個所に集めるなどの抽象化テクニックを川合氏は次々と披露（この間、会場には劇的ビフォーアフターのBGM）。オリジナルのCの関数をすっきりと整理していくことで、全体のコード行数を約半分に縮めることに成功。ソースコードが短くなっただけではなく、同系統の機能の追加が容易になったほか、パターンの再利用性が高まるといったメリットも加わったという。

もしこうした改修を本当に行ったとして、果たしてlsコマンドをメンテナンスするCの住人たちが暮らしてゆけるのかという疑問は残るが、劇的なビフォーアフターであることは間違いない。

オプションスイッチ処理関連のコードは、オリジナルのCでは3個所に分散していたが、マクロを用いたDSLによって各オプションは1行の宣言で済むように

lsコマンドのソースコードを匠が書き換えた結果、約半分の行数に！

言語「に」使われるのではなく、言語「を」使おう

マクロを使うことで一般にソースコードの行数は数分の1に縮むという。「ソースコードの規模が大きければ大きいほど効果が大きい」（川合氏）。

マクロの威力を示した川合氏だが、「だからといって今日うちに帰ってすぐに自分のプログラムをマクロで書き換えろという話ではない」と釘も刺す。「マクロは劇薬」（川合氏）だからだ。現在翻訳作業中の書籍「Programming Clojure」（Stuart Halloway著）の中から川合氏は、マクロに関する警句を引用する（ClojureはJVM上のLisp方言実装）。いわく、“マクロ倶楽部”には2つのルールと1つの例外があるという。

ルール1：マクロは書くな

ルール2：それがパターンをカプセル化する唯一の方法ならば、マクロを書け

例外：同等の関数に比べて、呼び出し側が楽になるならば、マクロを書いても構わない

S式やマクロの強力さを支持するハッカーは多いが、一般のソフトウェア開発で広く使われるテクニックとは言い難い。こうした事情も含めてか、最後に川合氏は「匠のメッセージ」として次のように述べた。

「プログラマであるあなたは、コンピュータに対して万能の神です。ですから、言語の制限にぶち当たって言語に使われるのではなく、言語“を”使って、言語と戯れましょう」。

「LLTVレポート 劇的ビフォーアフター、匠の技（後編）」では、サイロ化して連携利用が難しかった多数の業務システムを、bashスクリプトとフラットテキスト、多段パイプ処理の組み合わせというUNIX的アプローチで劇的にリフォームしてしまったUSP研究所の當仲寛哲氏の発表をレポートする。

（＠IT 西村賢） 情報をお寄せください：