
《Print Gallery》 作者:埃舍爾
本系列文章目錄:
上一篇文章 最后提到, 裝配腦袋 給出的 Fix 函數精簡到極致:
1 2 3 |
static Func<int, long> Fix(Func<Func<int, long>, Func<int, long>> f) { return x => f(Fix(f))(x); } |
下面我們看下是怎么推導出的,從 λ 演算入手:
λ 演算
根據 β-歸約 存在以下等式:
1 |
(λx.x(fix x)) f = f(fix f) |
根據 不動點組合子 的定義:
1 |
f(fix f) = fix f |
由以上兩個等式得出:
1 |
(λx.x(fix x)) f = fix f |
左右互換下:
1 |
fix f = (λx.x(fix x)) f |
根據 c a == d a 時 c == d,得出:
1 |
fix = λx.x(fix x) |
將 x(fix x) η-展開為 λn.x(fix x)n:
1 |
fix = λx.λn.x(fix x)n |
再變換一步:
1 |
fix = λx.λn.x(fix(x))(n) |
轉為 c# 代碼
Fix 是不動點算子,根據前文的假定和推斷它的類型是:Func<Func<Func<int, long>, Func<int, long>>, Func<int, long>>。(基於輸入是 int,返回值是 long 的假定)
借助 前言及基礎一文中總結出的小規律,可以寫出:
1 |
Func<Func<Func<int, long>, Func<int, long>>, Func<int, long>> fix = x(fix(x))(n) |
當然這個是編譯不通過的,讓我們把它改造成一個方法:
1 2 3 |
static Func<int, long> fix(Func<Func<int, long>, Func<int, long>> x) { return n => x(fix(x))(n); } |
將 int 改為 T、long 改為 TResult,抽象為泛型通用版本:
1 2 3 |
static Func<T, TResult> Fix<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> x) { return n => x(Fix(x))(n); } |
和 裝配腦袋 的對比下,一樣吧?好像參數名不同,沒關系,把參數 x 更改為 f:
1 2 3 |
static Func<T, TResult> Fix<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f) { return n => f(Fix(f))(n); } |
再比對下吧,一樣了吧!(如果還是看不出來的話,把 n 更改 x 試試)
后記
其實呢,我是反推出來的,先從 裝配腦袋 的 Fix 得出:
1 |
fix = λx.λn.x(fix(x))(n) |
然后運用 η-變換:λn.x(fix(x))(n) 簡化為 x(fix(x)):
1 |
fix = λx.x(fix(x)) |
兩側應用 f:
1 |
fix f = (λx.x(fix(x))) f |
運用 β-歸約 :
1 |
fix f = f(fix(f)) |
調整下:
1 |
fix(f) = f(fix(f)) |
正是不定點算子的定義。
把這個過程序反過來,就是本文 λ 演算 部分的內容。(如果用五個字評價我的這篇文章就是:事后諸葛亮。)
至於 裝配腦袋 怎么推出的這個 FIX,我就無從得知了。