Lispは1958年生まれの古参言語でありながら、マクロという仕組みによって現代でも群を抜く表現力を持っています。
しかし、それにもかかわらず使われるシーンが少ないのは「Lispの呪い」と呼ばれます。
逆説的ですが、その強さゆえにプログラマーが1人で完結できてしまい、エコシステムとして波及しにくかった。
今、AIがコードを書いてくれる時代に「1人で何でも作れてしまう」状況が一般化しつつあります。
もしかすると、同じ呪いが別の形で現れるかもしれません。
1. Lispは古くて新しい
プログラミング言語の歴史を振り返ると、Lispは不思議な存在です。
1958年にJohn McCarthyがMITでAI研究用言語として設計したこの言語は、ガベージコレクション1、高階関数2、動的型付けなど、現代の言語が「新機能」として導入したものを60年以上前から持っていました3。
それなのに、JavaやPython、JavaScriptほど普及しなかった。
なぜか。
この問いは、「Lispの呪い(The Curse of Lisp)」と呼ばれ、もしかすると生成AI時代のプログラム開発でも関係があるかもしれません。
2. Lispのマクロ
Lispが他の言語と根本的に違うのは、マクロ(macro)の存在です。
多くの言語でいう「マクロ」は、テキストの置換に過ぎません。
Cのマクロはただのコピペをコンパイラにやらせているだけです。
しかしLispのマクロは別物で、「コード自体をデータとして操作できる」機能です。
Lispではプログラムもデータも同じリスト構造で書かれます。
たとえばCommon Lispで (+ 1 2) と書くと、
これは「1と2を足す」という処理であると同時に、要素が3つのリストでもあります。
マクロはこのリストを受け取り、変換して、別のコードを生み出します。
つまり「言語の構文を自分で拡張できる」4。
具体例でみると、
Common Lispには標準で when という構文があります。
「条件が真のときだけ実行する」というシンプルなものです。
(when (> x 0)
(print "正の数です")
(do-something x))Code language: PHP (php)
実は、この when 自体が、マクロで定義されています。
(defmacro when (condition &rest body)
`(if ,condition
(progn ,@body)))Code language: CSS (css)
つまり when は言語に組み込まれた特別な構文ではなく、ユーザーが定義できるものと同じ仕組みで作られているのです。
これが意味することは大きいです。
「自分専用のプログラミング言語をLispの上に作れる」ということですから。
たとえばWebアプリのルーティング(URLと処理関数の対応付け)を定義したいとします。
PythonのFlaskならこう書きます。
@app.route('/user/<int:user_id>')
def get_user(user_id):
return fetch_user(user_id)Code language: JavaScript (javascript)
Common LispのWebフレームワークCavemanでも似たことができますが、マクロを使えばこういった構文を自分で設計できます。
(defroute ("/user/:id" :method :GET) (&key id)
(fetch-user (parse-integer id)))Code language: JavaScript (javascript)
この構文はマクロとして実装されていて、コンパイル時に適切なコードに展開されます。
Rubyのような言語でもDSL(Domain Specific Language、特定の用途向けに設計された小さな言語)を作れますが、Lispのマクロはそれをより根本的なレベルで実現します。
2.1. コード量の圧縮
マクロの効果は「コードが短くなる」だけではありません。
問題の構造に直接対応した言葉で書けるようになることです。
Lispに詳しいプログラマーが「他の言語なら10人必要な仕事を1〜2人でできる」と言うとき、誇張ではないことがあります。
特定の問題領域に特化したDSLを作ってしまえば、その上で書くコードは劇的に短く、バグも入りにくくなるからです。
3. 強さが引き起こす「個人完結」の罠
ただ、1人で何でも作れてしまうのは良し悪しなのかもしれません。
というのも、他の人と協力する必要がなくなるからです5。
たとえば、Javaで大規模なシステムを作る場合、1人では手が回りません。
設計者、実装者、テスト担当、ドキュメント担当、インフラ担当が必要で、自然とチームが生まれます。
そして、チームが機能するには標準化が必要。
「このプロジェクトではこのフレームワークを使う」「コードのスタイルはこう揃える」という合意が生まれます。
そこから共通のツールやライブラリが育ちます。
一方、Lispプログラマーにはその圧力がかかりにくい。
1人が問題の核心を解いてしまえるので、「チームで動かすための仕組み」を作るモチベーションが薄れます。
この傾向は「車輪の再発明(reinventing the wheel)」として現れます。
既存のHTTPライブラリがあっても、「自分のニーズには合わない、自分で書いた方が早い」と感じて別の実装を作る。
その実装は公開されるかもしれませんが、ドキュメントは薄く、メンテナは1人で、使い続けると方言の違いに悩まされます。
Common LispのパッケージマネージャーであるQuicklispで (ql:search "http") を試すと、似た目的のライブラリが十数個出てきます。
どれが「現在もメンテされているか」「本番で使えるか」は、自分で調べなければわかりません。
Pythonの requests のように、コミュニティが「これを使え」と合意した標準が育ちにくいのです6。
3.1. 90%で燃え尽きる問題
プログラマーが陥るもう一つの罠は、「問題の核心を解いた瞬間に満足してしまう」こと。
難しい問題の本質的な部分を異常に短時間で解けます。
そこに到達したとき、知的な達成感があります。
しかし、実はソフトウェアを「他人が使えるもの」にするには、その先の作業が全体の7割を占めます。
- エラーメッセージを人間が読める形にする
- インストール手順を整備する
- 他の環境での動作確認をする
- バグ報告に返答し続ける
- APIが変わったら後方互換性を保つか、移行ガイドを書く
これらは、地味で退屈な作業です。
身に覚えがありますが、個人プログラマーはこの部分を軽視しがち。
とりあえず、自分で使えたら十分だからです。
Quicklispで入手できるライブラリを使っていると、この現象に実際に出くわします。cl-json でJSONを扱おうとすると、エラーメッセージが最小限で、エッジケースの挙動はソースを読まないとわからない。
;; cl-jsonでのパース
(cl-json:decode-json-from-string "{\"name\": \"alice\", \"age\": 30}")
;; => ((:NAME . "alice") (:AGE . 30))
;; 不正なJSONを渡したときのエラーは、やや読みにくい
(cl-json:decode-json-from-string "{name: alice}")
;; => JSON-SYNTAX-ERROR が出るが、メッセージは最小限Code language: JavaScript (javascript)
PythonのJSONライブラリと比べると、エラー時のメッセージの丁寧さに明確な差があります。
これはLispが劣っているのではなく、「使いやすくする後半の作業」に割かれた時間の差です。
Lispのエコシステムは傾向として、動くけど、信頼して使い続けるには勇気がいる、という状態のライブラリが多くなるわけです。
3.2. 企業は「天才」を恐れる
ビジネスの観点から見ると、この問題は無視できません。
企業が求めるのは「最高のコードが書ける人」より「止まらない開発体制」。
そのため、「1人の天才に依存したシステム」を恐れます。
その人が退職したり、病気になったりした瞬間、開発が止まるからです。
JavaやC++は表現力がLispより低く、同じことをするのに多くのコードが必要です。
しかし逆に言えば、標準的なコードを作るインセンティブが働きます。
転職市場にJavaエンジニアは豊富にいて、1人抜けても補充できます。
そこで、意図的に、表現力の高い言語を避けるという合理的な判断が働きます7。
つまり、ある意味でLispの強さが生む皮肉です。
Lispが強力であるほど、その力を持つ人材は希少になり、組織としては採用しにくくなります。
4. 呪いから逃れた例外
ただし、もちろん「呪い」が絶対ではないことは、いくつかの例が示しています。
4.1. Clojure
ClojureはJVM(Java Virtual Machine、Javaを動かす実行環境)上で動くLisp方言です。
Javaのライブラリをそのまま使えるため、エコシステムの問題を回避しています。
MavenやLeiningenでJavaのライブラリを依存関係に加え、Lispの構文でJavaのAPIを呼べる8。
;; ClojureからJavaのArrayListを使う例
(import 'java.util.ArrayList)
(def my-list (ArrayList.))
(.add my-list "hello")
(.add my-list "world")
(println my-list) ;; => [hello, world]
ClojureがWebバックエンドやデータ処理の現場で実際に使われている事例があるのは、「Lispの強さ」と「Javaのエコシステム」を組み合わせたからです。
4.2. Emacs
EmacsはLisp(Emacs Lisp)で書かれたテキストエディタですが、数十年にわたって使い続けられています。
これは1,600人超のコントリビューターが地道にバグを直し、ドキュメントを書き続けた結果です9。
「プロトタイプで燃え尽きない」人たちが集まった例外的なコミュニティと言えるのかもしれませんね。
5. 結局のところ、呪いの正体は何か
Lispの呪いとは、強力な道具を持つと、「自分1人で解ける」という感覚が生まれること。
しかしソフトウェアが人に届くためには、解いた後の地道な作業が必要で、その作業は「問題を解く快感」とは別の種類の動機を必要とします。
Lispの強さは「問題を解く快感」を極限まで高める方向に作用します。
その副作用として、快感の薄い後半の作業が後回しになりやすい。
これが個人レベルで起き、コミュニティレベルで起き、言語のエコシステムに反映されているように見えます。
この呪いはLisp固有の問題であるというより、強力な道具には往々にして起こりうる問題だということです。
「プロトタイプは3日でできたのに、製品にするのに1年かかった」という経験は、言語を問わず多くのエンジニアにあるはずです。
Lispはそれを極端な形で、かつ文化として固定してしまったようです。
- 使い終わったメモリを自動で回収する仕組み
- 関数を引数として受け取る関数
- ガベージコレクションのアイデア自体もMcCarthyが最初に提唱したものであり、1959年にMITのDan Edwardsが実装した。McCarthyはもともとAI研究の記号処理に適した言語を求めており、FORTRANへの不満からLispの開発を始めた。 – Lisp (programming language) – Wikipedia
- コードとデータが同じ構造を持つこの性質はホモイコニシティ(homoiconicity)と呼ばれ、Lispが最初に実現した概念とされる。後のLisp系言語はほぼすべてこの特性を継承している。 – Lisp (programming language) – Wikipedia
- そういえば、最近はAIコーディングでLisp以外でもチームより個人開発が楽になっているかもしれませんね
- Quicklispはディストリビューション全体を月に1回程度スナップショットとして更新する方式をとっており、更新作業はZach Beane氏がほぼ単独で行っている。氏自身がブログで「私1人で作業しており、毎月何かが壊れていて定期リリースに戻れていない」と述べたことがある。より頻繁な更新を必要とする場合は5分ごとに更新されるUltralispというディストリビューションも存在する。 – Quicklisp news
- Common Lispは1994年12月にANSI規格(ANSI X3.226-1994)として標準化されたが、それ以降30年以上にわたって規格の更新が行われていない。Unicode対応、並行処理、モジュールシステムなど現代的な機能は各処理系(SBCL、Clozure CLなど)が独自拡張として対応しており、実装間の互換性に影響を与えている。 – Common Lisp – Wikipedia
- ClojureはRich Hickeyが2005年から約2年半を外部資金なしで単独開発し、2007年10月に公開したLisp方言。設計の動機の一つはJavaの並行処理の限界を克服することであり、不変データ構造とソフトウェアトランザクショナルメモリ(STM)を言語レベルで導入した点でCommon Lispとは思想が異なる。 – Clojure – Wikipedia
- Wikipediaの記述によれば、2025年時点でGNU Emacsには歴史を通じて1,608人の個別コミッターがいる。Emacsの開発は1970年代中頃にMIT AIラボで始まり、GNU EmacsはRichard Stallmanが1984年にGNUプロジェクトの最初のプログラムとして開発した。 – Emacs – Wikipedia