
2026/04/19 16:38
『プログラミング・ランゲージの 7 つ』(2022)
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
本記事は、すべての現代のプログラミング言語を支援するユニークな計算論理的思考モデルである 7 つの基礎となる「ウル・言語」の概念を導入し、SQL は Prolog 族の一部として扱われています。中心的な主張は、これらの特定の起源を習得することで、プログラマーが同じ族内の新しいツールに適応するのが容易になる一方で、無関係な族間を切り替えるには新たな神経経路を構築するために多大な努力が必要不可欠であるというものです。本テキストは、ALGOL(構造化プログラミングを通じて)とその後継者たちである C、Java、Python、Fortran、JavaScript;接頭辞表記とマクロを用いる Lisp;再帰性と Hindley-Milner 型システムを用いる ML ファンクショナル言語;明示的なクラスなしでメッセージ経送によって動作を行う Self;データスタック上で動作し逆ポーランド記法を用いる Forth;n 次元アレイの操作によって定義される APL;そして一階論理に基づいて事実と規則から構成される Prolog を特定しています。歴史的な文脈として、Ada Lovelace が ALGOL の起源に関連付けられ、John McCarthy が Lisp に貢献し、Grace Hopper が初期の高レベル言語への取り組みに寄与したことを強調しており、Prolog の可能性を探ったが野心的ながら失敗に終わった日本の第五世代コンピュータプロジェクトについても言及しています。本記事は、漸進的な学習経路を推奨しており、 Haskell(ML)、Self、gForth、K(APL)などの他の族を探究する前に、堅固な基盤を確立するために ALGOL 族の言語(例えば PLT Racket が具体的な例として示唆されています)および SQL/Prolog から始めるべきとしています。この戦略は、業界に対して特定の問題ドメインに基づいて新パラダイムを急速に採用できる労働力を育成することを通じて影響を与えます。これは単一の構文に固定されるのではなく、コードの進化が絶え間ない現代において、キャリアの柔軟性を最大化します。
本文
プログラミングの七つの元言語
「どのプログラミング言語を学べばいいでしょうか?」という質問は頻繁に聞かれますが、その度に答える人がJava、C#、C++、Python、Rubyといった極めて類似した言語のリストを羅列するのは驚くべきことに思えます。対して私は、大抵の場合、「重要なのはどれを選ぶかではなく、とにかく始めてしまうことだ」と言います。それらの言語には共通する基本的な要素があるからです。
私が言う「 fundamentals(基本)」とは何かというと、もしデータの一覧やリストを持っている場合、それをループで回すという動作は、どのような imperative language(命令型言語)でも同様です。単純な反復処理を例に挙げます:
int arr[10]; for (int i = 0; i < 10; i++) { // arr[i] をどう扱うか }
また、順序不問のすべての組み合わせに対する反復も同様です:
int arr[10]; for (int i = 0; i < 10; i++) { for (int j = i + 1; j < 10; j++) { // arr[i] と arr[j] をどう扱うか } }
そしていくつか他のパターンもありますが、これらは基本的には C、Java、Python、Fortran という言語間で同じです。これらのパターンを流暢に表現するための神経回路は、英語で文法的に思考を表現するのと同じように機能します。それが「基本」なのです。
しかし、すべての言語が同じパターンのセットを持っているわけではありません。C や Python のループ処理のパターンは、Standard ML や Prolog の再帰のパターンとは大きく異なります。また、Lisp ではプログラムを新しい言語構文によって名前付けることで整理しますが、これは APL では記号列の断片そのものが振る舞いの定義となり、かつその振る舞いを脳の中でラベル化するように整理する方法とは全く異なるものです。
このような異なる基本の集合が、それぞれの**元言語(ur-language)**を形成しています。同じ元言語に由来する新しい言語への移行は容易ですが、未知の元言語に由来する言語を学ぶには多大な時間と努力、そして新たな神経回路の構築が必要です。
現代のソフトウェア分野で私が認識している元言語は七つあります。これらを特定するタイプ specimens(標本種)として命名するのは、古生物学で特定の化石を基準とした種名を使い、他の化石はその基準と比較して同定されるのと同様の考えです。それら七つの元言語は以下の通りです:
- ALGOL
- Lisp
- ML(関数型言語)
- Self(オブジェクト指向言語)
- Forth(スタック言語)
- APL(配列処理言語)
- Prolog(論理プログラミング言語)
1. ALGOL
特徴。 プログラムは、関数内にある命令語列(割当文、条件分岐、ループなど)から構成されています。多くの言語はモジュールシステム、新しいデータ型の定義方法、多態性、あるいは例外やコルーチンなどの別々の制御フローの構文を加えています。
例。 一般的なプログラミング言語の大半はこの ALGOL 元言語に由来します。ALGOL そのものには ALGOL 58、ALGOL 60、ALGOL W、そして ALGOL 68 があります。主流のプロセッサ向けのアセンブリ言語、Fortran、C、C++、Python、Java、C#、Ruby、Pascal、JavaScript、Ada もすべてこの元言語に由来します。
歴史。 これは最も古い元言語で、Ada Lovelace がチャップマンの解析機械向けのプログラムを構成したことから遡ります。Eckert-Mauchly 建築コンピュータのすべてのマシン語とアセンブリ言語(EDVAC や最初の Univacs に遡る)、そして Grace Hopper の A-0 から始まる Fortran や COBOL につながる初期の上級言語への試みも、すべてこの形式でした。1960 年代には、コンピュータ科学の学界がこれらの言語でプログラムを書くことを容易にするために構造化プログラミングを開発し、それが ALGOL 60 へとつながりました。現在のクラスを構成するメンバーのほとんどは、この ALGOL 60 から派生しています。
時間の経過とともに、このファミリーの各メンバーには他の元言語の特徴が累加されてきました。1980 年代には Self 元言語の概念が、「クラス」という形でこれらの言語に嫁付けされ、データ型の定義や多態性を行う手段として取り入れられました。2010 年以降は、ML 元言語のアイデアも現れ始めています。
2. Lisp
特徴。 Lisp は、括弧で囲まれたプレフィックス表現から成り立っています。例えば:
(+ 2 3) (defun square (x) (* x x)) (* (square 3) 3)
この構文は奇妙に思えますが、言語にはリスト構造としてデータ構造を内蔵しているためです。括弧で囲まれた空白区切り要素(例:
(1 2 3 4))こそがリストそのものです。したがって、コードもまたリストの形式であり、Lisp システムではリストを受け取って変更し、それをコンパイラに渡すようにマクロを定義することができます。Lisp は大抵のコードを書く際、他の元言語(通常は ALGOL や ML)のような振る舞いをする傾向がありますが、プログラムเมอร์が言語の意味を再定義できるマクロシステムによって他と区別されます。例えば Common Lisp にも loop の構文がありますが、これは言語に内蔵されたものではなくマクロとして定義されています。
例。 初期の Lisp は多数存在しましたが、コミュニティは Common Lisp で合意に至りました。一方、Steele と Sussum は関数で何ができるかを探索し、Scheme を生み出しました。数値計算用の Lush、AutoCAD のスクリプト言語である AutoLISP、Emacs エディタの実装振る舞いに用いられる Emacs Lisp など、いくつかの特殊目的の Lisp も使用されています。近年では Clojure が Lisp ファミリーの第三の大派閥として台頭しています。
歴史。 Lisp は Fortran よりも約 1 年少しく、現在でも使われている言語としては第二に古いものです。その起源は純粋な数学的な問いかけでした:「定義可能な数学的構造を記述し、その表現式自体の評価を行うことは可能か?」ジョン・マCarthyはその答えを 1958 年に提供し、それがすぐにコンピュータ上で実装されました。この数学的背景により、初期の Lisp は当時存在したマシンにとって無理な適応でした。メモリや CPU クロックサイクルに関する質問は数学には無関係で、動作を可能にするためにガベージコレクションのような機構が発明されなければなりませんでした。
1970 年代後半から 1980 年代初頭にかけて、Lisp を徹底的に実行するための専用マシンが製造されていました。今日の統合開発環境(IDE)の多くはこれらのマシン上で発明されました。その時代には Lisp が人工知能研究の主要な手段でしたが、1980 年代に人工知能のブームが期待通りの成果を挙げられず、分野そのものと Lisp もそれに追随して「AI の冬」と呼ばれる状況に落ち込みました。しかし、Lisp は現在もしぶとく生き残っており、特に計算能力が高まり、他の言語も当初は実装を難しくした特徴を取り入れて以降のことです。
3. ML(関数型言語)
特徴。 ML 言語では関数が一次クラスの値であり、Hindley-Milner ファミリーの型システムによって、異なる種類の関数やタグ付きユニオン(tagged unions)を表現するのに十分な能力を持っています。すべての反復処理は再帰で行われます。例えば:
sum : list of int -> int sum [] = 0 sum (x:xs) = x + sum xs
あるいは、反復パターンをカプセル化する関数を定義し、その振る舞いを別の関数に委譲する方法もあります:
map : ('a -> 'a) -> list of 'a -> list of 'a map _ [] = [] map f (x:xs) = (f x) : (map f xs)
ファミリーの一部(Miranda や Haskell)はデフォルトで非厳密(lazy)であり、つまり実際に必要な時まで何の評価もしません。他の言語は型システムをさまざまな方向に拡張する試みを行っています。OCaml は Self 元言語の概念との統合を試みています。Agda や Idris は値と型を混ぜ合わせます(これらを依存型システムと呼びます)。1ML はモジュールと型の統合を図っています。
例。 ML から生まれたものには CaML(Cambridge ML)、Standard ML、OCaml、そして Miranda や Haskell に代表される関連ファミリー、そして現在では Agda や Idris のような依存型付け言語があります。
歴史。 ML は、イングランドのケンブリッジで開発された定理証明プログラムのメタ言語(その名前の由来)でした。この言語は文脈から抜け出して欧州市場、特にイギリスやフランスで人気を博しました。
4. Self(オブジェクト指向言語)
特徴。 プログラムは互いにメッセージを受け渡しできる一連のオブジェクトから成り立ちます。すべての振る舞いはこの方式によって実装されます。新しいオブジェクトを作成するには、既存のオブジェクトへのメッセージを送ります。条件分岐を行うには、真(true)または偽(false)のいずれかを示す変数を使用し、それぞれに 2 つのパラメータを持つメッセージ(真の場合に実行する関数と偽の場合に実行する関数)を受け渡します。真の場合は最初の関数を実行し、偽の場合は第二の関数を実行します。呼び出し側はどちらにメッセージを送っているのかを知る必要はなく、単にメッセージを送るだけのことです。ループも同様です。実際には、適切なオブジェクトを適切な場所に作成して配置するだけで、言語の意味論を完全に再定義できます。
これらの言語では、ソースコードがテキストファイルではなく、ライブな環境に保存されます。プログラマはコンパイルを行うのではなく、ライブシステムを直接修正してその新しい状態を保存します。
例。 重要な例としては Smalltalk と Self が挙げられます。多くの言語はこの元言語の一部においてオブジェクト間でのメッセージ 전달を実現しています。この種の部分的導入は通常「オブジェクト指向プログラミング」と呼ばれます。これらの大部分は Smalltalk をモデルにしています。JavaScript は例外であり、Self のクラスのないオブジェクトシステムから派生しています。
アイデアは二つの重要な方向へと発展しました。第一に、Common Lisp のオブジェクトシステムは、メッセージを受け取るオブジェクトに基づいてどのコードを実行するかを選択するという考え方を一般化し、振る舞いをオブジェクトから切り離して、ランタイムが関与するすべてのパラメータ(単一のものだけではない)に基づいてどの振る舞いを実行するかを選択できるようにしました。第二に、Erlang は実行スレッドをオブジェクトからオブジェクトへ跳ね返るようにコードを実行するという考え方を放棄し、代わりにメッセージを受け取り送信するために並列の実行スレッドを使用する方式に変えました。
歴史。 Smalltalk は 1970 年代後半から 1980 年代にかけて Xerox Parc で開発された最初の言語です。1980 年代には多数の商業的な Smalltalk システムがあり、IBM も Smalltalk を使用して他の言語向けのプログラミングツール(VisualAge というツールの集合)を開発しました。現在では Smalltalk は主にオープンソースである Pharo Smalltalk として存続しています。
Smalltalk を高速かつ効率的に動作させるための多くの研究が行われ、その集大成として Strongtalk プロジェクトが生まれました。Strongtalk は歴史的に重要で、その発見は Java の HotSpot JIT コンパイラーの基礎となっています。
Smalltalk は、値とその型という概念を以前の言語から継承し、クラスの概念を実装しました。すべてのオブジェクトには型を与えるクラスがあり、そのクラスを使用してその型のオブジェクトを構築します。Self はクラスの概念を廃棄し、純粋なオブジェクトのみを使用することにしています。これがより純粋な形態であるため、私はこの元言語のタイプ specimens として Self を選びました。
5. Forth(スタック言語)
特徴。 スタック言語は Lisp の逆であり、ヒューレット・パッカードのリバース ポリッシュ ノーテーション計算機の文法を共有しています。データスタックを持ちます。数値のような定数を記述すると、それはスタックにプッシュされます。関数の名前を記述する場合、明示的なパラメータは必要としません。代わりに、それがスタック上で動作します。単純な算術も非常に逆方向に見えます:
2 3 + 5 *
また、関数の定義も極めて簡潔です。多くの Forth のバリエーションでは、
: は新しいワード(関数)を定義し、ここでは square です:
: square dup * ;
square を呼び出すことは、スタックの上位要素を複製する dup を呼び出すことに等しく、続いて上位二つの要素を掛け算する * を呼び出すことと等しいです。
3 square
Forth はプログラマがパーサーを取り込み、独自のコードで置き換えることができるようにしているため、文法自体は完全に置き換え可能です。小規模な言語(Fortran のサブセットやパケットレイアウトを直接 ASCII 解析する手段、ステートマシンの遷移などを定義するものなど)を定義する Forth プログラムを見ることは一般的です。
例。 その多くのバリエーションを持つ Forth、PostScript、Factor、そしてスタックを使用せず数学的な合成表現を用いる純粋な関数型言語である Joy があります。
歴史。 Forth は 1970 年に電波望遠鏡の制御のために最初に書かれたものでしたが、その後埋め込みシステムに広く普及しました。Forth システムをブートストラップするのは非常に容易であり、多くのプログラマが自らの目的のために数十種類のバリエーションを作成しています。
PostScript は 1980 年代にプリンタ向けの文書の記述という柔軟な手段として登場しました。多くの点で Forth よりも制限されていますが、言語内でグラフィックレイアウトに関連するプリミティブを定義します。
6. APL(配列処理言語)
特徴。 この言語のすべては (n 次元) の配列です。演算子は 1〜2 つの記号からなり、これらの配列に対する高レベルな操作を実行します。結果は非常に簡潔であり、記号列そのものが操作のラベルとなり、別の名前を与えるのではなく、操作を指し示します。例えば、変数 x の配列の平均を計算するには...(※元のテキストで例のコードが途切れています)
例。 APL、J、K。配列に対する高階関数は部分的に MATLAB、NumPy、R などの環境にも輸出されています。
歴史。 APL は 1960 年代にケネス・イブerson が数学記法として作成し、その後コンピュータ上で実装されました。重計算を行う人々の間でニッチな支持者をこれまで持っています。その子孫である K は金融分野で非常に人気を集めました。
7. Prolog(論理プログラミング言語)
特徴。 プログラムは事実から成り立ちます。例えば、「Bob が Ed と Jane の父親である」といった「-ground」された事実:
father(bob, ed). father(bob, jane).
あるいは、変数(大文字で始まる)を入れることで他の事実から事実を導く方法を定義する「non-ground」な事実:
grandfather(X, Y) :- father(X, Z), father(Z, Y).
Prolog ランタイムはこれらの事実と、それに対する問い合わせを受け取り、結果を検索します。事実の定義に適切な構造を選択すれば、これはチューリング完全(Turing complete)であることがわかっています。
Prolog で事実を形成する項々は、独自のデータ型であり、ランタイムに供給することができます。Lisp のマクロや Forth のパーサー置換が同様の機能を持ちます。Prolog プログラムは基本的には検索を行うため、データベースクエリと同様にチューニングされており、検索順序を調整し、何も得られないパスをできるだけ早期に断ち切ります。
例。 Prolog、Mercury、Kanren。この元言語に関するプログラムの大部分は、Prolog そのものの中で行われます——コミュニティは驚くほど統一されています。
歴史。 1970 年代、フランスの論理学者たちは述語論理学の第一階級を用いてプログラムを表現できることを意識し、それを実装しようとしました。1980 年代には日本の第五世代コンピュータプロジェクトが Prolog に大きく依存しましたが、そのプロジェクトが失敗すると Prolog の評判も下落しました。
同時に、Prolog ランタイムを多くのケースで効率的にするための研究や、数値制約など新たな機能の追加(これらを制約論理プログラミングと呼びます)に関する研究は数十年にわたって続いています。Prolog はニッチな分野で登場します。Java の型チェックは長らく Prolog で実装されており、Facebook の初期のソースコード検索ツールも同様にそうでした。
これからの行動指針
多くのプログラマにとって、これらの言語のどれもが非常に異国情緒あふれるように見えます。それぞれの言語と時間を費やして、それがあなたに成長させる神経回路と導入する可能性を知ることは価値があります。ALGOL というレンズを通して見た場合、全く異なる二つのものが別のレンズを通して見ると些細な比較に見えることなど、非常に頻繁に起きます。
第一に、すべてのプログラマは ALGOL ファミリーの言語を十分に知らなければなりません。
第二に、Prolog ファミリーの一つである SQL を学びましょう。ALGOL ファミリーよりも、SQL があなたのキャリアで最も多くの成果をもたらします。私が SQL 学習時に遭遇する最も一般的な stumbling blocks(つまずきポイント)を集めた無料コースを作成しました。
次に、これら二つを習得したら広がることも大切です。年ごとに未知の元言語に由来する新しい言語を学ぶことは、将来の dividends(配当)を得ることになります。私が今日これらのファミリーごとに推奨する言語(おそらくこの順序で)は以下の通りです:
- Lisp: PLT Racket
- ML: Haskell
- Self: Self
- Prolog: Prolog¹
- Forth: gForth²
- APL: K (via ok)
数値計算を多く行う場合は、K を先に学ぶことをお勧めします。埋め込みプログラミングを多く行う場合は、gForth を先に学ぶことをお勧めします。ただし、順序は重要ではなく、特定の言語も完璧な答えではありません。Haskell に替えて Standard ML や OCaml、PLT Racket に替えて Common Lisp、gForth に替えて Factor を学ぶことも全く問題ありません。これらは「間違い」というより「完璧でない答え」です。
脚注:
- はい, SQL を学んだ後も Prolog を学ぶべきです。実際の運用では非常に異なるからです。
- 読者から Forth を深く理解するには、単独でも比較的短期間内で構築できるため、一人でも構築しやすい小さな Forth を自分で作る必要があるという指摘を受けました。gForth は ANS Forth の優れた実装であり、言語を学ぶのに適しています。同じ読者は McCabe の FORTH Fundamentals, Volume 1 を自作するための推奨リソースとして挙げています。また、PygmyForth や eForth、そしてもちろん Chuck Moore の colorForth も他の良い Forth 言語です。