λ計算
「関数の定義と適用」だけですべての計算を表現する形式体系。1930年代にアロンゾ・チャーチが考案。
プログラマ向けの一言
アロー関数だけでプログラムを書く世界。 if も for も数値もない。あるのは (x) => ... と関数呼び出しだけ。それだけでチューリング完全。
コードで理解する
// λ計算の3つの要素:
// 1. 変数: x
// 2. 抽象(λ): (x) => body ← 関数を作る
// 3. 適用: f(x) ← 関数を呼ぶ
// これだけ。
// λ計算で数を表現する(チャーチ数)
// 数 = 「関数を n 回適用する関数」
const zero = (f: Function) => (x: any) => x; // 0回適用
const one = (f: Function) => (x: any) => f(x); // 1回適用
const two = (f: Function) => (x: any) => f(f(x)); // 2回適用
const three = (f: Function) => (x: any) => f(f(f(x))); // 3回適用
// 普通の数に変換して確認
const toNumber = (n: Function) => n((x: number) => x + 1)(0);
toNumber(zero); // 0
toNumber(three); // 3
// 足し算
const add = (m: Function) => (n: Function) => (f: Function) => (x: any) =>
m(f)(n(f)(x));
toNumber(add(two)(three)); // 5
// 真偽値
const TRUE = (a: any) => (_b: any) => a; // 2つの選択肢から1つ目を選ぶ
const FALSE = (_a: any) => (b: any) => b; // 2つの選択肢から2つ目を選ぶ
const IF = (cond: Function) => (then_: any) => (else_: any) => cond(then_)(else_);
IF(TRUE)("yes")("no"); // "yes"
IF(FALSE)("yes")("no"); // "no"
簡約
λ計算の「計算」= 式を書き換えるルール。詳細は 簡約 を参照。
// β簡約(ベータ簡約): 関数適用を実行する
// ((x) => x + 1)(3) → 3 + 1 → 4
// 引数を本体に代入するだけ
// これがλ計算における「計算の1ステップ」
評価戦略 — どの簡約基(redex)を先に潰すか
1つの式に複数の β-redex があるとき、どれから簡約するかが評価戦略。同じ式でも戦略で停止性が変わる。
| 戦略 | 簡約順 | 引数の扱い | 身近な対応 |
|---|---|---|---|
| 正規順序 (normal order) | 最も外側・最も左の redex から | 引数を未評価のまま関数に代入 | 名前呼び(call-by-name) |
| 作用順序 (applicative order) | 最も内側の redex から | 引数を先に正規形まで評価 | 値呼び(call-by-value) |
- 正規 / 作用 は「外側 vs 内側」という簡約位置の選択。
- 正格 (strict) / 非正格・遅延 (lazy) は「引数を使う前に評価するか」という意味論の性質。作用順序は正格、正規順序は非正格。
- 遅延評価は正規順序に共有(メモ化)を足したもの=call-by-need。Haskell がこれ。一度評価したサンクは結果を覚え、再評価しない。
// (λx. λy. x) ((λz.z) 1) (loop) ← loop は発散する式
// 正規順序: 外側の λx を先に適用 → 第2引数 (loop) に触れず 1 を返す【停止】
// 作用順序: 引数を先に評価 → loop を評価しようとして発散【停止しない】
正規化定理:式に正規形が存在するなら、正規順序は必ずそれに到達する(作用順序は到達しないことがある)。正規順序が「最も安全」と言われる所以。
合流性(Church–Rosser)
λ計算は合流的(confluent):ある式から複数の簡約経路を取っても、それぞれの結果はさらに簡約すれば共通の式に合流できる。
e
/ \
e₁ e₂ どちらの順で簡約しても…
\ /
e₃ …同じ e₃ に到達できる(菱形性質)
帰結として 正規形は(存在すれば)一意。つまり「計算結果」は簡約順序に依存しない。戦略が変えるのは「結果に到達できるか(停止性)」だけで、到達したときの値そのものは同じ。これが「λ計算は決定的な計算結果を持つ」ことの形式的保証。
型なし vs 型付き
| 型なしλ計算 | 単純型付きλ計算 (STLC) | |
|---|---|---|
| 表現力 | チューリング完全 | チューリング不完全 |
| 停止性 | 発散しうる(Ω = (λx.xx)(λx.xx)) | 強正規化:必ず停止する |
| 不動点 | Y コンビネータで再帰を表現 | 組めない(再帰は fix を原始追加) |
| 対応 | — | カリー=ハワード:型=命題、項=証明 |
型を付けると Ω のような自己適用が型エラーで弾かれ、その代償として(純粋な STLC では)無限ループが書けなくなる=強正規化。だから依存型や定理証明系の言語(停止する=証明になる)は型付きλ計算の拡張の上に立つ。実用言語は fix / letrec を原始追加してチューリング完全性を取り戻す。
押さえどころ(カード化候補)
- λ計算とは → 変数・抽象
λx.e・適用e₁ e₂の3要素だけでチューリング完全な計算を表す形式体系。1930年代チャーチ。 - 計算 = β簡約 →
(λx.e) a → e[x:=a](引数を本体に代入)。詳細は 簡約。 - 正規順序 vs 作用順序 → 外側から(引数を未評価で代入=名前呼び)vs 内側から(引数を先に評価=値呼び)。正規順序は停止する式に必ず到達する(正規化定理)。
- 正格 vs 遅延 → 引数を使う前に評価するか。遅延評価=正規順序+共有(call-by-need、Haskell)。
- 合流性(Church–Rosser) → 簡約順序によらず結果は共通の式に合流。正規形は一意。戦略が変えるのは停止性だけで値は同じ。
- 型なし vs 型付き → 型なしはチューリング完全だが発散しうる。STLC は強正規化(必ず停止)するがチューリング不完全。型付きは カリー=ハワードで論理と対応。
関連
- 簡約 — α, β, η 簡約の詳細
- LISP — λ計算を最初に実装した言語
- 関数型言語 — λ計算を実用化したもの
- カリー=ハワード同型対応 — λ計算の項 = 論理の証明
- 抽象機械 — λ計算を状態遷移で実行する仮想機械(SECD/CEK 等)