プログラムは書くものではなく、組み替えるものだ

石川篤

Hatched by 石川篤

Apr 27, 2026

1 min read

88%

0

すべてのコードは、まずデータである

私たちはふだん、コードを「書くもの」だと思っている。頭の中にある仕様を文章のように並べ、コンピュータに命令を渡す。だが、もし本当に重要なのが「何を書くか」ではなく「何をデータとして扱えるか」だとしたらどうだろう。

この視点に立つと、プログラミングの風景は一変する。コードは固定された命令ではなく、読み取られ、変形され、再構成される素材になる。つまり、プログラムは完成品ではなく、編集可能な構造物だ。Clojure の世界では、この考え方が極めて徹底している。リーダーはコードをデータ構造へと読み取り、コンパイラはそのフォームをたどりながら必要ならマクロを呼び出し、マクロは自分自身の代わりになる新しいフォームを返す。

ここで重要なのは、これは単なる言語仕様の話ではないということだ。もっと深い問いがある。ソフトウェアは、人間が一度書いて終わる文章なのか。それとも、構造を保ったまま変換し続けられる生き物なのか。 この問いにどう答えるかで、設計の仕方も、抽象化の考え方も、バグとの付き合い方も変わる。


本当に欲しいのは「再利用」ではなく「変換可能性」

多くの開発者は、重複を減らしたい、保守しやすくしたい、似た処理をまとめたいと考える。その欲求は正しい。だが、その先にあるもっと強い欲求は、コードを再利用することではなく、コードを変換できることだ。

関数は再利用のための基本単位だ。同じ入力に同じ出力を返す形で、共通の処理を抽出できる。しかし関数には限界がある。関数はすでに評価された値を受け取る。だから、評価のタイミングを制御したいときや、呼び出し元の構文そのものを見たいときには足りない。そこでマクロが登場する。マクロは、値ではなくフォームを受け取り、コンパイル時にコードを別のコードへ変換する。

この違いは、実務では非常に大きい。たとえばログ付きの条件分岐を考えてみよう。普通の関数なら、引数は先に評価されるため、条件が偽でもコストの高い式が走ってしまう。だがマクロなら、必要な部分だけを展開できる。あるいは、繰り返し現れる定型コードをまとめるとき、ただ処理を抽出するだけでは足りず、呼び出し地点の文脈そのものを変えたいことがある。マクロはそこに手が届く。

関数が「値の再利用」なら、マクロは「構造の再利用」だ。

この差は見た目以上に深い。値を再利用する発想は、すでに完成したものをうまく扱う発想だ。構造を再利用する発想は、完成する前のものに手を入れる発想である。後者に入ると、プログラムは単なる命令列ではなく、生成のルールとして見えてくる。


マクロは魔法ではなく、責任の移転である

マクロは強力だが、何でもできるからこそ誤解されやすい。しばしば「高度な機能」として扱われるが、本質はもっと地味だ。マクロとは、関数では表現しきれない制御を、コンパイル時に移す仕組みである。つまり、実行時の複雑さを減らす代わりに、設計時の責任を増やす。

ここで起きるのは、単なる抽象化ではない。抽象化という言葉は便利だが、雑に使うと「何かを隠すこと」になってしまう。マクロが本当にやっているのは隠蔽ではなく、制御の位置をずらすことだ。評価の順序、名前の生成、繰り返しの展開、構文の形そのものを、コンパイル段階で決めてしまう。その結果、実行時には軽く、明確で、効率的なコードが残る。

この発想は、実は言語機能を超えて一般化できる。優れた設計とは、すべてを単純化することではない。むしろ、どの時点で何を決めるかを明確に分けることだ。実行時に決めるべきこと、コンパイル時に決めるべきこと、設計時に決めるべきこと。その境界が整理されるほど、システムは扱いやすくなる。

たとえば、フォーム入力の検証を考える。毎回実行時に大量の分岐で処理することもできるが、ルールの多くが静的なら、先に検証ルールからコードを生成するほうがよい。あるいは、SQLクエリの組み立てを考える。文字列を毎回足し合わせるのではなく、パターンを明確にして、そこから安全に生成する。ここでの本質は、人間が毎回手で書くのではなく、書くべきでない部分を構造化して機械に任せることだ。

マクロが危険だと言われるのは、強すぎるからではない。境界が曖昧になりやすいからだ。関数なら、入力と出力の関係を追えばよい。マクロはそれに加え、展開後のコード、評価の順序、生成される名前まで意識しなければならない。だからこそ、使う理由は「便利だから」では足りない。関数では不十分な場合にだけ使うという節度が重要になる。


Lisp的な発想が教える、抽象化のいちばん深い形

Lisp系の言語が長く愛される理由のひとつは、抽象化の単位が極端に少ないことだ。コードはデータであり、データはコードになりうる。この単純さが、逆説的に非常に強い表現力を生む。なぜなら、言語の中心にあるものが最初から「変換可能」だからだ。

多くの言語では、構文は固定されている。ユーザーはその上で関数を組み合わせ、せいぜいライブラリの範囲で世界を広げる。だが Lisp 的な世界では、言語そのものの拡張が、ライブラリの延長として現れる。コア構造の多くが、組み込みプリミティブではなく、ユーザーが書けるのと同じ種類のマクロとして実現できるという事実は、単に技術的に面白いだけではない。それは「言語とは何か」という定義を変えてしまう。

ここでの核心は、表現力が増すことそのものではない。重要なのは、表現の境界を自分で押し広げられることだ。固定された文法に従うのではなく、自分たちの問題に合った表現を作る。これは、ドメイン固有言語の夢ともつながるが、もっと根本的には、ソフトウェアを「規則に従う作業」から「規則を設計する作業」へと引き上げる。

たとえば、会計、イベント処理、状態遷移、データ変換など、繰り返しの多い領域では、毎回バラバラにロジックを書くより、構造を先に定義したほうが圧倒的に強い。Lisp 的な世界は、その構造化を単なる設計思想ではなく、言語レベルの操作対象にしてしまう。これは、抽象化を「上に積む」のではなく「下から掘り起こす」感覚に近い。

良い抽象化とは、詳細を隠すことではない。詳細を、適切なタイミングで生成できることだ。

この一文が示すのは、抽象化の中心が「省略」から「生成」へ移っていることだ。隠されたものは後で困る。生成できるものは後で修正できる。ここに、保守性の本当の意味がある。


実践のためのフレームワーク: 3つのレイヤーで考える

この視点を日常の設計に落とし込むには、コードを次の3つのレイヤーで見るとよい。

1. 値のレイヤー

ここでは通常の関数を使う。すでに決まった入力に対して、明確な出力を返す。副作用をできるだけ減らし、テストしやすくする層だ。

2. 構造のレイヤー

ここではマクロ的な発想が生きる。繰り返される構文、評価順序の制御、名前の生成、定型の展開などを扱う。人間が毎回書くとミスが増える部分を、形としてまとめる。

3. 言語のレイヤー

ここでは、問題領域に合わせて新しい表現を作る。ルールエンジン、パイプライン、宣言的な設定、状態遷移表など、単なる関数合成では足りないときに、構造そのものを設計する。

この3層で考えると、多くの設計ミスが見えるようになる。たとえば、値のレイヤーで十分なものをわざわざ構造のレイヤーに押し上げると、複雑さだけが増える。逆に、構造のレイヤーが必要なのに値のレイヤーに閉じ込めると、コードはすぐ破綻する。問題は「何を抽象化するか」ではなく、どの層で抽象化するかにある。

具体例を挙げよう。もしアプリ内に似たような権限チェックが何十個も散らばっているなら、単純な関数化で十分な場合もある。しかし、権限ルール自体が複雑で、順序や例外、条件付きの展開が必要なら、より高いレベルの構造化が必要になる。そこでは、単にコードをまとめるだけでなく、コードがどう生成されるべきかを設計する必要がある。


Key Takeaways

  • 関数とマクロを、便利さの違いではなく責任の位置の違いとして見る。 関数は値を扱い、マクロは構造を扱う。
  • 「再利用したい」と感じたら、一段深く「変換したいのか」を問う。 その答えが、関数で足りるかマクロ的発想が必要かを分ける。
  • 評価順序や名前生成を意識するコードは、構造のレイヤーに移す。 実行時の分岐で無理に解決しない。
  • 言語仕様をただ使うのではなく、問題領域に合わせて表現を作る発想を持つ。 それが抽象化の本当の強さになる。
  • 抽象化の目的は隠蔽ではなく、適切なタイミングでの生成にある。 後から変更できる形を優先する。

では、私たちは何を書いているのか

ソフトウェア開発は、しばしば「正しい答えをコードに落とす作業」だと思われている。だが、より本質的には、正しい変換規則を見つける作業だ。入力から出力へ至る経路を、どう構造化すれば人間にも機械にも扱いやすいかを考える。そのときコードは、静的な記述ではなく、変形されるべき形になる。

Clojure が示すのは、コードをデータとして扱える世界では、抽象化は単なる整理術ではなく、表現の再発明になるということだ。マクロは派手な技巧ではない。むしろ、どこまでを値として扱い、どこからを構造として扱うかを冷静に決める技術だ。そしてその判断こそが、優れたソフトウェアと、ただ動くソフトウェアを分ける。

最後に残る問いはこうだ。あなたが書いているのは、命令の列なのか、それとも変換可能な構造なのか。 この問いを持てるようになると、コードの見え方が変わる。関数はもはや唯一の抽象化ではない。コードそのものを素材として扱える瞬間から、プログラミングは「書くこと」ではなく、「組み替えること」になる。

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣