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:#fff
構文族の系統。ALGOL 60のブロック構造が多くの族の源流にあり、Lisp系だけが独自の系譜を持つ

C系 — 世界を席巻した中括弧

もっとも普及した構文族です。ブロックを で囲む書き方は、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 + 2f(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の「セミコロンが式を文に変える」という象徴的な設計を見ました。 記法には前置・中置・後置があり、前置と後置は優先順位ルールを不要にします。

次章では、これらの構文が何を「体現」しているのか——命令型・オブジェクト指向・関数型・論理型・宣言型という パラダイムと構文の関係に踏み込みます。「プログラミング・パラダイム」という言葉自体の起源から始めましょう。

理解度チェック

問題 0 / 50%
Q1

Pythonがブロックをインデント(字下げ)で表す規則を何と呼びますか?また、それを名づけた人物は誰ですか?

キーボード: 1〜4 で選択、Enter で回答