第1章で出会った糖衣構文(syntactic sugar)——ピーター・ランディンが1964年に名づけた「人間のための甘味料」——を、 本章で正面から扱います。構文は単に「正しいかどうか」だけでなく、書きやすさ・読みやすさ・表現力を生み出す装置でもあります。 糖衣構文がどう「素朴な形」に戻されるのか(脱糖)、そして構文そのものを拡張するマクロやDSLという強力な技法へと進みます。
脱糖 — 甘さの裏にある素朴な形
糖衣構文の定義は「取り除いても言語の表現能力は変わらない構文」でした。ということは、どんな糖衣も より基本的な構文へ書き直せるはずです。この書き直しを脱糖(desugaring)と呼び、 多くはコンパイラが内部で自動的に行います。実行時のコストはほぼゼロ——あくまで「書くときの楽さ」のための仕組みです。
graph LR S["糖衣構文\n人間が書く"] D["脱糖 desugar\nコンパイラが変換"] C["素朴な形\n言語のコア構文"] M["機械が実行"] S --> D D --> C C --> M style S fill:#ec4899,stroke:#db2777,color:#fff style D fill:#8b5cf6,stroke:#6d28d9,color:#fff
身近な糖衣をいくつか、脱糖の前後で並べてみましょう。普段なにげなく使っている構文の「正体」が見えてきます。
// アロー関数
const add = (a, b) => a + b;
// 脱糖 → 通常の関数式(thisの扱いは厳密には異なる)
const add = function (a, b) { return a + b; };
// 分割代入
const { name, age } = user;
// 脱糖 →
const name = user.name;
const age = user.age;
// スプレッド
const merged = { ...obj1, ...obj2 };
// 脱糖 →
const merged = Object.assign({}, obj1, obj2); # リスト内包表記(数学の集合の内包表記が起源)
result = [x * 2 for x in range(10) if x % 2 == 0]
# 脱糖 → 素朴なループ
result = []
for x in range(10):
if x % 2 == 0:
result.append(x * 2)
# with 文(コンテキストマネージャ)
with open("f.txt") as f:
data = f.read()
# 脱糖 → __enter__ / __exit__ の呼び出しと try/finally 相当に展開される async/await — もっとも劇的な脱糖
糖衣の中でもっとも「大掛かりな脱糖」をするのが async/await です。見た目は同期コードのように上から下へ流れますが、
内部では状態機械(state machine)へと変換されます。コンパイラは「どの await まで進んだか」を
状態番号で管理し、非同期処理が終わったらその位置から実行を再開するのです。
// 糖衣構文 — 同期コードのように読める
async function getFoo(bar) {
const baz = 2 * bar;
const root = await asyncSqrt(baz); // ここで一時停止し、後で再開
return 2 * root;
}
// 脱糖のイメージ — Promiseチェーン(さらに深くは状態機械)
function getFoo(bar) {
const baz = 2 * bar;
return asyncSqrt(baz).then(root => 2 * root);
} 構文塩 — わざと書きにくくする
糖衣(sugar)があるなら、その逆もあります。構文塩(syntactic salt)です。これは 「プログラマに意図を証明させるため、あえて冗長にする構文」を指します。動作を表現するためではなく、 誤りを書きにくくするために存在します。
| 言語 | 構文塩の例 | 防ぎたい誤り |
|---|---|---|
| C# | メソッド上書きに override 必須 | 偶発的なオーバーライド |
| C# | switchのフォールスルーに goto case が必要 | 意図しない処理の続行 |
| Java | チェック例外の throws 宣言 | 例外の握りつぶし |
| TypeScript | 危険な型変換は as unknown as T の二段階 | 不用意な型の強制 |
「書きやすさ」だけが正義ではない、という点が重要です。安全性のためにあえて手数を増やすのも、 立派な構文設計の判断です。糖衣と塩は、同じ「人間の認知」に向けた、方向の異なる二つの配慮なのです。
マクロ — 構文そのものを拡張する
糖衣は言語設計者があらかじめ用意するものですが、マクロを使えばプログラマ自身が新しい構文を作れます。 マクロは「コードを受け取り、別のコードを生成する」仕組みです。その強力さと安全性には、言語によって大きな段階差があります。
Cのマクロ — 危険なテキスト置換
Cのプリプロセッサマクロは、コンパイル前の単純なテキスト置換に過ぎません。型もスコープも理解しないため、 落とし穴だらけです。
#define SQUARE(x) (x * x)
int r = SQUARE(a++); // → (a++ * a++) に展開され、a が2回インクリメントされる! Lispのマクロ — 同図像性の力
第3章で見たLispの同図像性(コード=データ)が、ここで真価を発揮します。コードがそのままリストなので、 マクロは「リストを受け取り、リストを返す関数」として、コードを自由自在に組み立て直せます。
;; my-when という新しい制御構文を自作する
;; コードはリストなので、list と cons で「if の式」を組み立てて返す
(defmacro my-when (condition &body body)
(list 'if condition (cons 'progn body)))
(my-when (> x 0) (print "positive") (incf x))
;; 展開 → (if (> x 0) (progn (print "positive") (incf x))) Rustのマクロ — 衛生的で安全
Rustの macro_rules! はパターンマッチに基づき、しかも衛生的(hygienic)です。
マクロ内で使う変数名が呼び出し側の変数と衝突しないことが自動的に保証されます
(Cのマクロには無い安全性)。
// 可変長引数を受け取るマクロ(標準の vec! に近い構造)
macro_rules! my_vec {
($($x:expr),*) => {{
let mut v = Vec::new();
$( v.push($x); )* // 引数の数だけ push を繰り返す
v
}};
}
let v = my_vec![1, 2, 3]; | 観点 | C プリプロセッサ | Lisp マクロ | Rust macro_rules! |
|---|---|---|---|
| 動作レベル | テキスト置換 | コード(S式)操作 | トークン木のパターンマッチ |
| 同図像性 | なし | あり | なし(別途トークンで扱う) |
| 衛生性 | なし(衝突しうる) | 手動(gensym) | 自動(衛生的) |
| 安全性 | 低い | プログラマ責任 | コンパイラが保証 |
DSL — 問題領域の言葉でプログラムを書く
マクロや高階関数、柔軟な構文を組み合わせると、DSL(ドメイン固有言語)——特定の問題領域の語彙で 書ける小さな言語——が作れます。独自の文法とパーサを持つ外部DSL(SQL、HTML、Dockerfile)と、 ホスト言語の構文を流用する内部DSLがあり、後者はRubyやKotlinの得意技です。
# Ruby内部DSL(RSpec)— これは全部ふつうのRubyのメソッド呼び出し
describe Calculator do
it "adds two numbers" do
expect(calc.add(2, 3)).to eq(5)
end
end
# Ruby内部DSL(Railsのルーティング)
Rails.application.routes.draw do
resources :users
get "/about", to: "pages#about"
end describe・it・expect は新しい構文に見えますが、その実体はメソッド呼び出しと
ブロックです。ホスト言語(Ruby)の構文が柔軟で、括弧を省略でき、ブロックを渡せるからこそ、まるで専用言語のような
「読める」コードが書けるのです。これも広い意味では「構文の表現力」が生み出す価値です。
表現力 vs 一貫性 — 二つの哲学
ここまで「表現力を増す」方向ばかり見てきましたが、表現力には影もあります。「いろいろな書き方ができる」ことは、 「読み手が多様な書き方に対応せねばならない」ことでもあるのです。この緊張を象徴するのが、 PerlとPythonの対照的なスローガンです。
| Perl の哲学 | Python の哲学 | |
|---|---|---|
| スローガン | TIMTOWTDI「やり方は一つじゃない」 | 「明確な、できれば唯一の方法があるべき」 |
| 提唱者 | ラリー・ウォール | ティム・ピーターズ(Zen of Python) |
| 重視するもの | 表現力・自由・個性 | 一貫性・可読性・予測可能性 |
| 学習コスト | 高い(多くの書き方を覚える) | 低い(覚えることが少ない) |
| チーム開発 | レビューに時間がかかりがち | 均質なコードを保ちやすい |
まとめ — 構文は表現力の装置
本章では、構文が「書きやすさ・表現力」を生む仕組みを掘り下げました。糖衣構文は脱糖を経て
素朴な形に戻され(async/awaitは状態機械へ)、逆に構文塩はあえて冗長にして誤りを防ぎます。
マクロは構文そのものを拡張する技法で、Cのテキスト置換からLispの同図像性、Rustの衛生的マクロへと
安全性が高まります。それらはDSLを生み、そして表現力と一貫性はPerlとPythonに象徴される
根本的なトレードオフでした。
ここまでは「構文とは何か・どう設計するか」を見てきました。次章からは時間軸を導入します。なぜパラダイムは 生まれ、移り変わるのか——パラダイムシフトの力学へ。クーンの科学哲学、ソフトウェア危機、 そして「言語は思考を規定するのか」という深い問いが待っています。
理解度チェック
コンパイラが糖衣構文(syntactic sugar)をより基本的な構文へ書き直す処理を何と呼びますか?
キーボード: 1〜4 で選択、Enter で回答