簡約(α, β, η)

λ計算における式の書き換えルール。計算 = 簡約を繰り返して最終的な値に到達すること。

プログラマ向けの一言

関数呼び出しを「展開」して結果を得る手順のこと。 α は変数の名前変え、β は関数呼び出しの実行、η は無意味なラッパーの除去。

β 簡約(ベータ簡約)— 最も重要

// 関数に引数を渡して、本体の中の変数を置き換える
// これが「計算」そのもの

// (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 は正規順序に近い = 遅延評価(必要になるまで計算しない)

押さえどころ(カード化候補)

関連

Graph