代码改变世界

使用 Lambda 表达式编写递归五:推导装配脑袋的 Fix

2013-04-11 10:31  鹤冲天  阅读(3668)  评论(7编辑  收藏  举报
Print Gallery
《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,我就无从得知了。