簡約(α, β, η)
λ計算における式の書き換えルール。計算 = 簡約を繰り返して最終的な値に到達すること。
プログラマ向けの一言
関数呼び出しを「展開」して結果を得る手順のこと。 α は変数の名前変え、β は関数呼び出しの実行、η は無意味なラッパーの除去。
β 簡約(ベータ簡約)— 最も重要
// 関数に引数を渡して、本体の中の変数を置き換える
// これが「計算」そのもの
// (x => x + 1)(3) →β 3 + 1 → 4
const f = (x: number) => x + 1;
f(3); // 4 ← β簡約が起きた
// もっと複雑な例
// (x => y => x + y)(2)(3)
// →β (y => 2 + y)(3) ← 最初の x に 2 を代入
// →β 2 + 3 ← 次の y に 3 を代入
// → 5
const add = (x: number) => (y: number) => x + y;
add(2)(3); // 5
α 簡約(アルファ簡約)— 変数の名前変え
// 変数名を別の名前に一貫して置き換える
// 意味が変わらなければ OK
// (x => x + 1) と (y => y + 1) は同じ関数
// α-equivalent(α等価)という
const f1 = (x: number) => x + 1;
const f2 = (y: number) => y + 1;
// f1 と f2 は「α等価」= 名前が違うだけで同じもの
// なぜ必要か? → 変数名の衝突を避けるため
// (x => (x => x)(x))(5)
// 内側の x と外側の x が衝突 → 内側を別名に変える
// (x => (z => z)(x))(5)
// →β (z => z)(5)
// →β 5
η 簡約(イータ簡約)— 無駄なラッパーの除去
// 「引数を受け取って、そのまま別の関数に渡すだけ」の関数は無駄
// η簡約前: (x) => f(x)
// η簡約後: f
// この2つは同じ
const double = (x: number) => x * 2;
// 冗長(η展開された形)
const redundant = (x: number) => double(x);
[1, 2, 3].map((x) => double(x));
// η簡約後(直接渡せばいい)
[1, 2, 3].map(double);
// ただし JS/TS では this バインディングの問題で
// 常に η 簡約できるわけではない
簡約戦略
// 複数の簡約が可能なとき、どの順序でやるか?
const expr = ((x: number) => (y: number) => x + y)(1 + 1)(2 + 2);
// 正規順序(leftmost-outermost first): 外側から
// → ((y) => (1+1) + y)(2+2) → (1+1) + (2+2) → 2 + 4 → 6
// 作用順序(leftmost-innermost first): 引数を先に評価
// → ((x) => (y) => x + y)(2)(2+2) → ((y) => 2 + y)(4) → 2 + 4 → 6
// JS/TS は作用順序(引数を先に評価)= 正格評価
// Haskell は正規順序に近い = 遅延評価(必要になるまで計算しない)
押さえどころ(カード化候補)
- 簡約とは → λ計算の式の書き換えルール。計算 = 簡約を繰り返して値に到達すること
- β簡約(最重要)→ 関数に引数を渡し本体中の変数を置き換える=「計算」そのもの。(x=>x+1)(3) → 3+1 → 4
- α簡約 → 変数名を一貫して付け替える(意味不変=α等価)。目的は変数名の衝突回避。同名変数のシャドーイングを別名化する
- η簡約 → (x)=>f(x) を f に縮める無駄なラッパー除去。ただし JS/TS では this バインディングの都合で常には適用できない
- 簡約戦略 → 正規順序(外側から/遅延評価・Haskell) vs 作用順序(引数を先に評価/正格評価・JS-TS)。両者とも合流すれば同じ値に至る