PythonとLispとCを並べて見たとき、まったく別世界の見た目に感じられます。しかし、その「見た目」の違いは 無秩序ではなく、いくつかの構文族(syntax families)に整理できます。第2章で「文法という設計図」を 手にしたいま、本章はその設計図から生まれた多彩な「外見」を地図にします。さらに、外見以前のもっと根本的な分かれ道—— 「式」と「文」という区別——にも踏み込みます。これは後の章で扱うパラダイムの土台になります。
構文族の地図
代表的な構文族と、その起源・特徴を俯瞰しましょう。多くはたった一つの祖先言語から枝分かれしています。
| 構文族 | ブロックの表し方 | 代表言語 | 起源 |
|---|---|---|---|
| C系(中括弧族) | { } で囲む | C, C++, Java, JS, C#, Go, Rust | BCPL→C(さらに遡るとALGOL) |
| ALGOL/Pascal系 | begin ... end | Pascal, Ada, Ruby(do/end), Lua | ALGOL 60 |
| インデント族 | インデントの深さ | Python, Haskell, F#, Nim | Landinのオフサイドルール |
| S式族 | 括弧 ( ) の入れ子 | Lisp, Scheme, Clojure | McCarthyのS式(1958) |
| ML系 | let ... in / match | ML, OCaml, Haskell, Elm | Milnerの ML(1973) |
graph TD
ALGOL["ALGOL 60\nblock構造"]
BCPL["BCPL\n{ } を導入"]
C["C系\n中括弧族"]
PAS["Pascal/Ada系\nbegin-end"]
LISP["Lisp\nS式・前置記法"]
ML["ML系\n型推論・match"]
PY["インデント族\nPython等"]
ALGOL --> BCPL
BCPL --> C
ALGOL --> PAS
ALGOL --> ML
LISP --> ML
style C fill:#3b82f6,stroke:#2563eb,color:#fff
style LISP fill:#8b5cf6,stroke:#6d28d9,color:#fff
style PY fill:#14b8a6,stroke:#0d9488,color:#fffC系 — 世界を席巻した中括弧
もっとも普及した構文族です。ブロックを で囲む書き方は、C言語が前身のBCPL(1966)から
受け継いだもので、それをデニス・リッチーがCに採用しました。Wikipediaが「C系言語」として列挙する言語は約70にのぼります。
C・Java・JavaScript・C#・Go・Rust・Swift・Kotlin——現代の主流言語の大多数がこの族です。
// C — 中括弧でブロック、文末にセミコロン
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
} インデント族 — オフサイドルール
Pythonに代表される族では、ブロックをインデント(字下げ)の深さそのもので表します。この規則を オフサイドルール(off-side rule)と呼び、名づけたのは第1章でも登場したピーター・ランディン(1966)。 サッカーのオフサイドにかけた言葉遊びです。「前の行の先頭より左に出たトークンは、新しいブロックの始まり」という規則です。
# Python — インデントがそのままブロック。波括弧もセミコロンもない
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
利点は明確です。見た目の構造と論理構造が必ず一致する——インデントがずれていれば、それはバグです。
逆にC系では、インデントは飾りに過ぎず、コンパイラは しか見ません。だから「見た目は正しいのに
ブロックの範囲が違う」というバグが起こりえます。一方でインデント族には「コピペで壊れやすい」「タブとスペースの混在」
といった批判もあり、Goの設計者はあえて を選びました。構文の選択には常にトレードオフがあります。
S式族 — 括弧の中に世界がある
Lispの族は徹底しています。ジョン・マッカーシーが1958年に考案したS式(S-expression)を基本構造とし、
コードもデータもすべて同じ「括弧で囲まれたリスト」として書きます。形式は (演算子 引数 引数 ...) という
前置記法に統一されています。
; Scheme — すべてが (演算子 引数...) の前置記法
(define (factorial n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
(+ 1 (* 2 3)) ; => 7。括弧が構造を決めるので優先順位ルールが不要 この「コード=データ」という性質を同図像性(homoiconicity)と呼びます。プログラムがそのまま リストとして操作できるため、コードを生成・変形するコード——マクロ——が極めて強力になります (第5章で詳しく扱います)。括弧の多さは敬遠されがちですが、その均質さこそがLispのメタプログラミング能力の源です。
式と文 — 構文設計の根本的な分かれ道
外見の話から、もっと深い構造の話に移ります。式(expression)と文(statement)の区別です。 これは単なる用語ではなく、言語の性格を決定づける根本的な設計判断です。
| 概念 | 定義 | 例 |
|---|---|---|
| 式(expression) | 評価すると値を返す構文要素 | 1 + 2、f(x) |
| 文(statement) | 値を返さず、副作用や制御を行う構文要素 | if (...) { ... }、for (...) |
C・Java・Pythonのような文指向言語では、if は文であり、値を返しません。
だから「条件で値を決める」には、いったん変数を用意して各分岐で代入する必要があります。
// Java(文指向)— if は文なので、値を入れる変数が必要
int x;
if (condition) {
x = 42;
} else {
x = 0;
}
対して、Rust・Ruby・Scala・Haskellのような式指向言語では、if も式であり、値を返します。
「すべてが式(everything is an expression)」という設計です。
// Rust(式指向)— if が値を返すので、そのまま代入できる
let x = if condition { 42 } else { 0 };
// ブロックも式。最後の式がブロックの値になる
let y = {
let a = 3;
let b = 4;
a * b // セミコロンなし → この値 12 がブロックの値
};
// match も式
let label = match x {
0 => "zero",
_ => "nonzero",
};
式指向は、代入の重複を減らし、不変変数(val/let)と相性がよく、関数型プログラミングと自然に融合します。
この「式中心」という性質が、第4章で見る関数型パラダイムの構文的な基盤になります。
記法の三つ巴 — 前置・中置・後置
演算子をどこに置くか、にも流儀があります。私たちが慣れた 1 + 2 は中置記法ですが、
これが唯一の方法ではありません。
| 記法 | 書き方 | 優先順位ルール | 代表言語 |
|---|---|---|---|
| 中置(infix) | 1 + 2 * 3 | 必要(* が + より優先 等) | C, Java, Python, Rust |
| 前置(prefix) | (+ 1 (* 2 3)) | 不要(括弧で構造が決まる) | Lisp, Scheme, Clojure |
| 後置(postfix) | 1 2 3 * + | 不要(スタックで処理) | Forth, PostScript |
中置記法は人間に自然ですが、「* は + より先」という優先順位と
「- は左から、べき乗は右から」という結合性のルールが必要になります。
一方、前置(Lisp)と後置(Forth)は、これらのルールが一切不要です。括弧やスタックの構造が
計算順序を完全に決めてしまうからです。
\ Forth — 後置記法(逆ポーランド記法)。スタックで計算する
25 10 * 50 + \ (25 × 10) + 50 = 300
. \ 結果を出力 後置記法は計算機にとって処理が容易(スタックに積んで演算子で取り出すだけ)なため、初期の電卓や仮想マシンの バイトコードで好まれました。記法の選択は「人間が読みやすいか」と「機械が処理しやすいか」のせめぎ合いでもあるのです。
同じプログラムを五つの族で
最後に、同じ「フィボナッチ数」の計算を構文族ごとに並べて、外見と思想の違いを体感しましょう。 同じことをしているのに、まとう「文化」がこれほど違います。
# インデント族(Python)— 字下げがブロック
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2) -- ML系(Haskell)— パターンマッチで場合分け、型注釈は推論可能
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2) ; S式族(Scheme)— すべて前置記法、括弧の入れ子
(define (fib n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1)) (fib (- n 2)))))) // C系 + 式指向(Rust)— 中括弧だが if が値を返す
fn fib(n: u64) -> u64 {
if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}
Haskellのパターンマッチ(fib 0 = 0 のように引数の形で場合分け)に注目してください。
これは次章で扱う関数型パラダイムを象徴する構文です。構文族の地図を手にしたいま、私たちはようやく、
構文の背後にあるパラダイム——プログラミングの様式そのもの——へと踏み込む準備ができました。
まとめ — 外見と根本
本章では、言語の「見た目」をC系・ALGOL系・インデント族・S式族・ML系という構文族に整理し、 それぞれの起源(BCPLの中括弧、ランディンのオフサイドルール、マッカーシーのS式)を辿りました。さらに外見より深い 「式と文」の区別を学び、Rustの「セミコロンが式を文に変える」という象徴的な設計を見ました。 記法には前置・中置・後置があり、前置と後置は優先順位ルールを不要にします。
次章では、これらの構文が何を「体現」しているのか——命令型・オブジェクト指向・関数型・論理型・宣言型という パラダイムと構文の関係に踏み込みます。「プログラミング・パラダイム」という言葉自体の起源から始めましょう。
理解度チェック
Pythonがブロックをインデント(字下げ)で表す規則を何と呼びますか?また、それを名づけた人物は誰ですか?
キーボード: 1〜4 で選択、Enter で回答