使用 Lambda 表达式编写递归五:推导装配脑袋的 Fix
2013-04-11 10:31 鹤冲天 阅读(3684) 评论(7) 编辑 收藏 举报《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,我就无从得知了。
-------------------
思想火花,照亮世界