1. 計算の二つの起源
Lispを書いていると、不思議な感覚に陥ります。(length '(1 2 3))と書いたとき、Rubyの配列が.lengthに応答するときと同じ手触りがあります。
一方で(lambda (x) (* x x))や末尾再帰を書いていると、プログラムというより数式を展開しているような気分になります。
動的でオブジェクトっぽく、同時に数学的で関数型っぽい。
この二つの雰囲気が同居している理由は、Lispが「オブジェクト指向言語」でも「関数型言語」でもなく、それらが分岐する前の場所に立っているからです。
1936年、二人の数学者がほぼ同時期に、互いに独立した「計算のモデル」を発表しました。
アラン・チューリングはチューリングマシンを考案しました。
無限のテープに記号を読み書きしながら状態を遷移させていく、抽象的な機械です。
「計算する」という行為を、状態とメモリの操作として定式化しました。
アロンゾ・チャーチはラムダ計算を提案しました1。
こちらにはテープもメモリも登場しません。
式を別の式に変換するという操作だけで計算を記述します。
変数はメモリの場所ではなく、式の中の束縛です。
この二つはまったく異なる見た目をしていますが、どちらも同じものを計算できると証明されました。
これがチャーチ=チューリングのテーゼで、計算可能なものはどちらのモデルで記述しても同じということを意味します2。
1.1. ノイマン型とLispの分岐
1940年代、ジョン・フォン・ノイマンはチューリングマシンの発想を実装可能な形に落とし込みました3。
プログラムとデータを同じメモリに格納し、CPUが命令を順番に実行していく構造、いわゆるノイマン型アーキテクチャです。
アセンブリ、Fortran、Basic、Cはこのアーキテクチャの自然な延長として生まれました。
変数はメモリのアドレスに名前をつけたものです。int x = 5と書くとき、xはどこかのメモリ番地を指しています。
型はコンパイラが知っていますが、実行時には消えます。sizeof(int)はコンパイル時に解決されます。
Lispは違う道をたどりました。
ジョン・マッカーシーが1958年に設計したLispは、ラムダ計算を直接ソフトウェアとして表現しようとした言語です。
同じノイマン型ハードウェアの上で動きますが、その発想の根はまったく異なります。
2. メモリから切り離された値
Cの値は本質的にメモリ上のビット列です。
型情報はコンパイル時に使われ、実行時には残りません。
関数に値を渡すとき、その値が「何であるか」を実行時に問い直すことができないのはそのためです。
Lispの値は違います。
すべての値が実行時に型タグを持っていて、処理系はいつでも「これは何か」を答えられます。
(numberp 42) ; => T
(listp '(1 2)) ; => T
(length '(a b c)) ; => 3Code language: Lisp (lisp)
numberpもlengthもメソッドではありません。42.isNumber()でも[a,b,c].lengthでもない。
それでも「値が実行時に自分の型を知っている」という前提は、Rubyのオブジェクトと同じです。
ガベージコレクションもここから来ています。
値がメモリのアドレスと切り離されているから、処理系がメモリを管理できます。
Cではプログラマがメモリを解放しますが、Lispではその責任を処理系が引き受けます4。
ラムダ計算に「値をどこかに置く」という概念がないことと対応しています。
2.1. 関数が第一級オブジェクトであること
Cにも関数ポインタがあります。
関数のアドレスを変数に入れて渡すことができます。
しかし関数ポインタは「アドレスを渡せる」というだけで、自分が定義されたスコープの変数を持ち歩くことができません。
Lispのラムダは、定義されたスコープを閉じ込めます。
(defun make-adder (n)
(lambda (x) (+ x n)))
(defvar add5 (make-adder 5))
(funcall add5 3) ; => 8Code language: Lisp (lisp)
make-adderが返すラムダは、nの値を保持したまま別の場所で呼ばれます。
これがクロージャです。
関数が値として扱われ、スコープを閉じ込め、別の関数に渡したり返したりできます。mapやreduceも同じ仕組みの上に成り立っています。
(mapcar (lambda (x) (* x x)) '(1 2 3 4 5))
; => (1 4 9 16 25)Code language: Lisp (lisp)
関数型言語が前提とする「関数を値として扱う」能力は、Lispに最初から備わっていました。
2.2. コードとデータが同じ形をしている
もう一つ、Lispに固有の性質があります。
コードとデータが区別されません。
Lispのプログラムはリストで書かれます。(+ 1 2)はリストであり、同時に「+を1と2に適用する」という式です。
だからevalが成立します。
(eval '(+ 1 2)) ; => 3Code language: Lisp (lisp)
データとして作ったリストをそのままコードとして実行できます。
マクロはこの性質を使って言語の構文を拡張します。
チューリングマシンでもラムダ計算でも直接は出てこない発想で、Lispが独自に持っている特徴です。コードが第一級オブジェクトになっている、とも言えます5。
3. パラダイムという分節化
「手続き型」「オブジェクト指向」「関数型」という言葉は、それぞれの言語の特徴を観察して後から名付けたものです。
主要言語を年代順に並べると、各パラダイムがどこから来てLispとどう関係するかが見えてきます。
3.1. 1950年代
Fortran(1957)はIBMが科学計算のために作った最初の高水準言語です。
数式を直接コードとして書けることが革命的でしたが、発想はノイマン型そのもので、変数はメモリのアドレスに名前をつけたものにすぎません。
型はコンパイル時に処理され、実行時には消えます。
COBOL(1959)はGrace Hopperが主導し、米国防総省の要請で設計しました。MOVE x TO y、SUBTRACT income tax FROM payのように英語の文として読めることを徹底しています。
数学的な抽象とは正反対の方向、「ビジネスの担当者が読めるコード」という極端な実用主義です。
Hopperは「数学者はシンボルを扱うのが好きだが、データ処理担当者は英語の文で書きたい」と語っています。冗長で300以上の予約語を持ちますが、今なお世界の金融取引の大半を処理しています6。
ALGOL(1958〜1960)はFortranへの反省から欧米の研究者委員会が設計しました。
ブロック構造と字句スコープを導入し、再帰呼び出しも持ちました。
構造化プログラミングの土台を作った言語で、Tony Hoareは「その時代より先を行っていた。後継言語の多くより優れていた」と評しています7。
直接広まることはありませんでしたが、Pascal、BCPL、B、Cへと連なる系譜の起点になりました。
BASIC(1964)はDartmouth大学でFortranとALGOLを簡略化した教育用言語として生まれました。
対話的に実行できることが新しく、1970〜80年代のマイコンブームに乗って爆発的に普及しました。
Prolog(1972)はチューリングマシンにもラムダ計算にも属さない第三の計算モデルから来ています。
論理プログラミングと呼ばれるパラダイムで、プログラムはファクトとルールの集合です。
「何をするか」を宣言するだけで、「どうするか」は処理系が論理的推論で決めます。
一階述語論理を直接実行可能にしようとしましたが、主流にはなれませんでした。
C(1972)はUnix開発のためにBell研究所でDennis Ritchieが作りました。
ノイマン型の発想を最も素直に言語にしたもので、ポインタでメモリを直接操作します。
型はコンパイラだけが知っていて、実行時には何もありません。
awkとsedは1970年代のUnixテキスト処理ツールで、プログラミング言語というよりDSLです。
パターンに一致したらアクションを実行するという宣言的なスタイルを持ちます。
awkの限界がLarry Wallに1987年のPerlを書かせました。
Perlはawk、sed、Cを混ぜ合わせながら、Lispから暗黙のreturnや式としての文という発想も吸収しています。
C++(1983)はCにクラスを後付けしました。intのようなプリミティブはノイマン型的なビット列のままで、std::stringとは別物として残ります。
3.2. 1990年代
Haskell(1990)は副作用を完全に排除した純粋関数型言語です。
式は必要になるまで評価されない遅延評価を採用し、型クラスとモナドで副作用を型に閉じ込めます。1987年に研究者委員会が設計を始め、Lispの系譜から出発しながらLispとは対極の純粋さを追求しました8。
Python(1991)はすべての値が実行時に型を持ち、関数も第一級オブジェクトです。
前身はABCという教育用言語で、動機は読みやすさと実用性でした。mapやlambdaは後から加わり、van Rossum自身はあまり好んでいなかったと述べています。
Lispが数学から来た言語なら、Pythonは教育から来た言語といえます。
Java(1995)はJames GoslingがC++の複雑さを取り除き、JVM上のバイトコードとして「Write Once, Run Anywhere」を実現しました。
完全なOOPを目指しましたが、実行効率への妥協でintとIntegerが分裂しています。
どちらも手続き型とOOPの間で設計上の分裂を抱えています。
Ruby(1995)はMatzがEmacs Lispを書いた経験からオブジェクトモデルを発想し、Smalltalkのメッセージパッシングを乗せ、Perlの実用性を加えた言語です。
「コアはシンプルなLispで、オブジェクトシステムはSmalltalk風」とMatz自身が説明しています。
すべてが実行時に型を持つオブジェクトであり、ブロックはLispのクロージャ文化を意識的に継承しています。
JavaScript(1995)は、作者Brendan EichがもともとブラウザにSchemeというLispの方言を実装する予定でした。
ビジネス上の事情でJavaに似た構文にせざるを得ませんでしたが、クロージャはSchemeから、プロトタイプベースの継承はSelfというSmalltalk系の言語から持ち込みました9。
Eich自身が後に、プリミティブとオブジェクトの分裂は「不幸だった」と述べています。
表面はJavaに似せながら、内側にはLisp系とSmalltalk系の設計が同居しています。
3.3. 2000年代
C#(2000)はMicrosoftがJavaへの対抗として設計しました。
Anders HejlsbergがTurbo PascalとDelphiで培った経験をベースにしており、出発点はJavaに近いOOPでしたが、C# 3.0でLINQ、ラムダ式、型推論を一気に取り込みました。
設計者自身が「関数型の世界から多くを借りてきた」と語っています。
Clojure(2007)はRich HickeyがJVM上に実装したLispの方言です。
Common LispやSchemeの系譜に立ちながら、不変データ構造とSTMで並行処理の安全性を確保します。
JavaエコシステムとLispの思想を接続した現代的な実践例です。
Go(2009)はKen ThompsonとRob PikeらUnixの作者たちが設計しました。
C++の複雑さへの反動として継承もジェネリクスも当初は省き、GCと、CSPに基づく並行処理をゴルーチンとチャネルで表現します。
「21世紀のC」と呼ばれることもあります。
TypeScript(2012)はJavaScriptに静的型付けを足した言語で、同じくAnders Hejlsbergが設計しました。
実行時の姿はJavaScriptのままで、開発時だけ型が見えます。
Rust(2015)はGCを持たず、コンパイル時の借用チェッカーでメモリ安全性を保証します。
不変性、パターンマッチング、代数的データ型といった関数型の要素を取り込みながら、到達点は「メモリの正しさを型システムで表現する」という独自の問いです。Linuxカーネルへの採用が進んでいます10。
GoとRustはどちらもCの後継を名乗れますが、問いの立て方が違います。
Goは複雑さを解こうとし、Rustは安全性を解こうとしました。
4. 「書きやすさ」は分化する
年代順に並べると、1950年代に生まれたFortranとALGOLとLispが、その後数十年の言語設計に大きな影響を与えたことがわかります。
Lispの「値が実行時に型を持つ」「関数が第一級オブジェクトである」という性質は、OOPと関数型の両方に共通する前提になりました。
各パラダイムはその性質の一部を取り出して名前をつけた、とも言えます。
ノイマン型ハードウェアは効率のために設計されています。
メモリのアドレスを直接操作し、命令を順番に実行します。
これに最も忠実な言語がCで、速いですが人間には読みにくい。
すべての言語は、この効率性を一定程度手放す代わりに、何らかの「書きやすさ」を得ようとしています。
しかし書きやすさの方向は一つではなく、少なくとも三つに分化しました。
一つ目は数学的抽象化の方向です。
ラムダ計算を起源とし、式を評価して変換するという発想で、ループより再帰、状態より関数、副作用より純粋性を好みます。
Haskell、ML、Schemeがここに属します。
Lispはこの系譜の源泉で、「計算とは何か」という問いに数学の言葉で答えようとしています。
二つ目は工学的な部品組立の方向です。
OOPがここで、現実のシステムをオブジェクトという部品に分解して組み立てます。
Smalltalk、Java、C#がその典型で、数学的な純粋さより設計の管理しやすさを優先しました。
「大きなシステムをどう分割するか」という工学上の問いに答えようとしています。
三つ目は実用・対象領域への接近の方向です。
COBOLがその極端な例で、ビジネス文書のように読める英語的な構文を選びました。
Perlはテキスト処理という領域に特化し、Pythonは人間の認知に寄り添う読みやすさを優先しています。
「その領域の人間が自然に読み書きできるか」という問いに答えようとしています。
これら三つは互いに排他的ではありません。
Rubyは工学的OOPと数学的クロージャと実用主義を全部持っています。
JavaScriptはSchemeのクロージャとSelf由来のプロトタイプと、10日で書いた場当たり的な設計が同居していて、どの方向にも中途半端に属しています。
それが強さでもあります。
Lispはこの分化が起きる前の場所にいます。
数学的抽象であるラムダ計算と、OOPの前提となる実行時型情報と、evalとマクロを可能にするコード・データの均質性を、分けることなく一つの言語として持っていました。
4.1. 地層が透けて見える
Rubyで[1,2,3].map { |x| x * x }と書くとき、裏にある仕組みは「値が実行時に型を持ち、ブロックがクロージャである」ということです。
JavaやC++でプリミティブ型とオブジェクト型が分離している理由は、後からOOPを足したからで、設計の最初から均質ではありませんでした。
Lispを書くと、それらの設計判断がどこから来ているかが透けて見えます。
パラダイムの名前がなかった時代に、計算とは何かという問いに直接向き合って作られた言語だからです。
カッコの多さに最初は戸惑うかもしれません。
でも慣れてくると、Lispが持っている静けさの理由がわかってきます。
設計に余分なものが少ない。
必要なものが、必要な形でそこにあります。
- チャーチがラムダ計算の原型を発表したのは1932〜1933年で、Turingの論文より先です。1936年の論文で計算可能性の定義として完成させ、チューリングが同年独立に等価性を証明しました。 – Church–Turing thesis – Wikipedia
- チューリングの論文は「On Computable Numbers, with an Application to the Entscheidungsproblem」(1936〜1937年)。チューリング自身が付録でラムダ計算との等価性を示しました。 – Computability and Complexity – Stanford Encyclopedia of Philosophy
- 1945年6月に回覧された「First Draft of a Report on the EDVAC」が出典です。ただしEckertとMauchlyはストアードプログラムの発想は自分たちのものだと主張しており、「ノイマン型」という命名には異論もあります。 – Von Neumann architecture – Wikipedia
- ガベージコレクションはMcCarthyが1959〜1960年頃にLispのために発明したもので、プログラミング史上初のGCとされています。マーク&スイープアルゴリズムとして1960年の論文「Recursive Functions of Symbolic Expressions and Their Computation by Machine」で初めて記述されました。 – Garbage collection (computer science) – Wikipedia
- evalはMcCarthyが理論として記述したものを、大学院生のSteve Russellが「これは機械語に翻訳できる」と気づいて実装したのが始まりです。McCarthyは当初これが動くとは思っていなかったといいます。 – History of Lisp – John McCarthy
- COBOLは世界の金融取引の約90%、ビジネスデータの約75%を処理しているとされます。米国の銀行ATMのほとんどがCOBOLで動いており、2025年時点でも基幹システムとして現役です。 – Grace Hopper and Colleagues Introduce COBOL – History of Information
- Tony Hoareのこの評言はALGOL 60 Reportへの言及として広く引用されています。BNF記法(バッカス・ナウア記法)もALGOL 60のために開発されたもので、現代のプログラミング言語の文法記述に使われています。 – ALGOL 60 – Wikipedia
- 言語名「Haskell」は数学者・論理学者のHaskell Brooks Curry(1900〜1982)にちなんでいます。Curryは組合せ論理を発展させ、関数型言語の理論的基盤を築きました。「カリー化」という概念名も彼の名前に由来します。 – Haskell Language Report Preface
- EichはNetscapeに1995年4月に入社し、5月の10日間でJavaScriptの最初のプロトタイプ(当時の名称はMocha)を完成させました。EichはSchemeとSelfを主な参照としたと自身のブログで述べており、Javaの影響は構文と一部のAPIにとどまると評価しています。 – Brendan Eich’s blog – Tag: history
- Rustは2022年12月リリースのLinux 6.1でカーネルに初めてマージされました。2025年12月には実験的フラグが外れ、Rustによるカーネル開発が公式に正式化されています。 – Rust for Linux – Wikipedia