Lispが60年以上たっても重要な理由
Lispは誕生65年以上経った今も言語設計に影響を与え続けています。その生命力の理由と、現代の開発者が学べることを解説します。

毎年のように「Lispは死んだ」というエッセイが書かれ、そして同じくらいの頻度で、お気に入りのモダンな言語の機能の半分はLispから盗まれたものだ、と指摘する人が現れます。ガベージコレクション、第一級関数、クロージャ、動的型付け、ホモイコニシティ、REPL駆動開発、マクロ。今日の開発者の大半が生まれる前に、これらはLispで発明されたか広く普及したものです。Lispはじっくり時間をかけて初めて真価がわかるタイプのプロジェクトなのです。
では、現役の開発者にLispを使ったことがあるか聞いてみてください。大半は「ない」と答えるでしょう。本番プロジェクトをLispで始めるかと聞けば、変な目で見られるはずです。ここにはパラドックスがあります。Lispのアイデアはプログラミングの世界を征服したのに、Lisp自体はニッチな言語のままなのです。なぜそうなったのかを理解するほうが、多くのLisp推しの記事にある「Lispは良い、他の言語はダメ」という主張よりずっと面白い話です。
Lispが本当に正しかったこと
Lispは1958年にJohn McCarthyによって作られました。当時の感覚で言えば、FORTRANがまだ1歳の頃です。COBOLはまだ存在しませんでした。PDP-1ミニコンピュータが出荷されるのは、さらに2年後のことです。Lispは紙の上で設計され、IBM 704(部屋を埋め尽くすほど大きく、36ビット語を扱うマシン)に実装されました。
それでも、McCarthyの設計判断の多くは60年経った今も色褪せていません。特に重要なものを挙げます。
コードはデータ(ホモイコニシティ)
多くの言語では、コードとデータは根本的に別物です。ある構文でコードを書き、それがデータ構造を操作します。Lispでは、コードそのものがデータです。Lispのプログラムはリストで構成されています。式 (+ 1 2) は、1と2を足す関数呼び出しであると同時に、+ というシンボル、数値 1、数値 2 の3要素からなるリストでもあるのです。そのリストを並べ替えたり、要素を追加したり、変換したりしてから、その結果を実行できます。
;; This is a function call
(+ 1 2) ; => 3
;; This is a list containing the same elements
'(+ 1 2) ; => (+ 1 2)
;; You can build code as data and then evaluate it
(def my-expr '(+ 1 2))
(eval my-expr) ; => 3
;; Or transform it
(def doubled (list '* 2 my-expr))
;; doubled is now (* 2 (+ 1 2))
(eval doubled) ; => 6
これは最初は小手先の面白さに思えますが、そこから何が可能になるかに気づくと見方が変わります。プログラムがプログラムを書けるのです。Lispのマクロは、C言語のプリプロセッサマクロのような単純な文字列置換ではありません。実際のプログラム構造をデータ構造として受け取り、言語の全機能を使って変換し、新しいプログラム構造を返します。これはコンパイル時のコード生成であり、コンパイラそのものがAPIになるようなものです。
REPLを開発環境として使う
LispはRead-Eval-Print Loop(REPL)の先駆けです。式を入力すると、すぐに評価されて結果が表示される。このアイデアです。今やほぼすべての言語にREPLがあるので平凡に聞こえるかもしれませんが、LispのREPLはたいていのものより一歩先を行っています。
Common LispやClojureでは、REPLで単独の式をテストするだけではありません。システム全体を対話的に開発するのです。関数を定義してテストし、プログラムを動かしたまま再定義します。実行中の状態を調べたり書き換えたりもできます。アプリケーションを再起動せずに、関数単位で再コンパイルすることもできます。開発サイクルは「書いて、コンパイルして、実行して、デバッグ」ではなく、生きたシステムとの絶え間ない対話になります。
特にClojureの開発者はこの方法で作業することが多いです。エディタを実行中のREPLに接続し、エディタでコードを書き、キーを一つ押すだけで個々の式を評価して即座に結果を確認します。フィードバックループはミリ秒単位で、分単位ではありません。一度この方法を経験すると、従来の編集・コンパイル・再起動のサイクルは、手紙でやり取りしているように感じられるでしょう。
最小限の構文、最大限の柔軟性
Lispの構文、あるいは構文の無さは、最も賛否が分かれる特徴です。if 文、for ループ、クラス定義のための専用の特殊形式はありません。すべてが先頭に演算子を置いたリストです。(if condition then-expr else-expr)、(defn name [args] body)、(for [x (range 10)] (* x x)) といった具合です。初心者が敬遠する括弧は、これまで設計された中で最も構文的に規則的な言語に入るための代償なのです。
この規則性は実用面でも恩恵があります。ツールとの相性です。すべての構文要素が同じ (operator operands...) のパターンに従うので、エディタは確実にインデントやリファクタリングを行い、構造に沿ってコードを移動できます。「囲んでいる式を選択」は、Lispでは常に対応する括弧を指すので曖昧さがありません。構文が多様な言語では、構造的な編集は未解決の問題です。Lispでは1960年代からすでに解決されています。
Lispが勝てなかった理由
Lispがそれほど優れているなら、なぜ誰もが使っていないのでしょうか。Lisp推しの定番の答えは「業界が間違っている」というものですが、それは役に立たず、ほぼ誤りです。Lispが主流に普及しなかったのには、現実的で些末ではない理由があります。
- エコシステムの差。 実務の多くでは、言語機能よりもライブラリのほうが重要です。Pythonが人気なのは構文が美しいからではなく、
pip installを使えば、データサイエンス、Web開発、機械学習などあらゆる分野の数千もの保守されたパッケージに即座にアクセスできるからです。Lispのエコシステム(Common Lisp、Scheme、Clojure)はしっかりしていますが、規模は小さめです。ゼロから書いたり、メンテナンスの行き届いていないライブラリを調整したりする時間が多くなるでしょう。 - 学習コストが序盤に集中している。 Lispの括弧付きの構文は、一度身につけば驚くほど単純です。しかし初心者にとっては現実的な壁です。さらに重要なのは、イディオマティックなLispを書くには、手続き型やオブジェクト指向の言語に慣れた人には馴染みのない思考法が必要になることです。見返りは後から訪れますが、多くの開発者はそこに辿り着く前に離脱してしまいます。
- 分裂している。「Lisp」は一つの言語ではなく、ファミリーです。Common Lisp、Scheme、Racket、Clojure、Emacs Lisp、Hy、Janetなど、それぞれ強みも、エコシステムも、コミュニティも異なります。この分裂が、エコシステムの成長に必要な努力の集中を妨げています。
- 企業の後ろ盾は重要。 Javaには Sun、C#には Microsoft、Go には Google がありました。Python には Google と巨大なデータサイエンスのコミュニティがあります。本番環境で最も成功しているLispファミリーの言語であるClojureが広まったのは、Rich Hickeyが非常に明晰な思考と発信を持つ人物だったことも一因です。しかし、主流への普及を後押しする組織的な推進力はまだ欠けています。
Clojure:歴史から学んだLisp
Clojureは特筆に値します。Lispの強みを、現代のソフトウェア開発で実用的にする方法を示しているからです。Rich Hickeyは2007年にClojureを設計し、以前のLispが主流に普及しなかった理由を明確に意識した上で、意図的なトレードオフを選びました。
- JVM上で動作。 ゼロから独自のエコシステムを作る代わりに、ClojureはJava Virtual Machine上で動き、あらゆるJavaライブラリを直接使えます。データベースドライバが必要ですか。Javaのものを使えばいい。HTTPクライアントが必要ですか。Javaのものを使えばいい。この一つの判断によって、Clojureは初日から実戦で鍛えられた数千のライブラリを利用できました。
- デフォルトで不変。 Clojureのコアデータ構造(リスト、ベクタ、マップ、セット)は不変です。マップを変更するのではなく、変更を加えた新しいマップを作ります。これにより並行性バグの一大カテゴリが消え、プログラムの推論が容易になります。内部の永続データ構造は構造を共有することで、このアプローチを効率的にしています。
- 実用的な並行性プリミティブ。 Atom、Ref、Agentなど、Clojureは用途に応じた複数の並行性モデルを提供します。これは理論的な美しさのためではなく、本番のJavaアプリケーションで共有された可変状態に苦しんだHickey自身の経験から生まれたものです。
- ClojureScript。 ClojureはJavaScriptにコンパイルされ、ブラウザやNode.jsのエコシステムを利用できます。バックエンドはClojureで、フロントエンドはClojureScriptで書き、両者でコードを共有できます。これはTypeScriptやKotlin Multiplatformと同じ約束ですが、それより早く登場しました。
現代の言語が借用したもの
Lispのコードを一行も書かなくても、日々その思想を使っています。その影響はあまりにも広く浸透しているので、どこまで深く根付いているかを追跡すること自体が一つの課題になるほどです。
JavaScriptの map、filter、reduce は、Lispのリスト処理関数の直系の子孫です。言語名そのものがそこから来ています(LISt Processing)。Pythonのリスト内包表記は、同じ操作の構文上の砂糖衣です。Rustのパターンマッチは、MLを経由してLispの cond 式にまで遡れます。Swiftのクロージャ、Kotlinのラムダ構文、Javaのストリーム API。これらはすべて、別の服を着たLispの概念なのです。
Rustの宣言的マクロ(macro_rules!)と手続き型マクロ、Elixir、Nim、Julia のマクロシステムは、Lispのマクロから直接インスパイアされています。ただし、どれも同じ滑らかさには届いていません。ホモイコニックな構文を持っていないからです。コードがデータであれば、マクロは簡単です。コードが複雑な構文を持つ場合、マクロはその構文のパースと生成が必要になり、摩擦が生じます。
REPL駆動開発は、データサイエンスではすっかり当たり前になりました(Jupyter notebookは、事実上、永続化機能を持つREPLです)。FigwheelやSHadow-cljsはフロントエンド開発にホットリロードをもたらしました。これは実行中のシステムに対して開発するというLispの伝統から直接来たアイデアです。
Lispを学ぶべきか?
正直な答えは、何を得たいかによります。
コードの捉え方を広げて、より良いプログラマーになりたいなら、間違いなくイエスです。Lispを学ぶ、特にデータ変換、再帰的な構造、コードはデータであるという考え方を身につけると、どの言語でのアプローチも変わります。関数型プログラミングを学ぶのと似ています。本番でHaskellを使わなくても、その概念によって、PythonやJavaScriptのコードがより良くなるのです。
Lispで本番のソフトウェアを作りたいなら、実用的な選択肢はClojureです。実際のエコシステムがあり、プロフェッショナルなコミュニティがあり、Nubank、Walmart、CircleCIといった企業が大規模に運用しています。Common Lispも実用可能ですが、ニッチです。本来解決したい問題よりもインフラ周りに時間を取られることが多いでしょう。SchemeとRacketは教育や言語研究には優れていますが、汎用的な開発にはあまり実用的ではありません。
興味はあるけれどまだ腰を据える準備はできていないなら、Clojureのコードを読んでみてください。チュートリアルではなく、実際の本番のコードです。Clojureの開発者が小さな関数をどう組み合わせるか、-> や ->>> のスレッディングマクロをどうやってデータパイプラインの構築に使うか、クラスではなくプレーンなマップでドメインをどうモデル化するかを見てください。主流のOOPのコードベースで目にするものよりも簡潔で、組み合わせやすく、データフローに重点を置いたプログラミングのスタイルが見えてくるはずです。
Lispが生き残っているのはノスタルジーではありません。いくつかの基本的なことを極めて正しく作ったことの結果であり、60年間の言語設計でもそれを超えられていないのです。括弧は確かに奇妙ですし、エコシステムは思ったほど大きくありません。しかしアイデアは普遍的で、それらに時間を割けば、日常で使っている言語でも、より良いコードが書けるようになるはずです。


