使用 Lambda 表达式编写递归三:实现 Y 组合子
2013-04-10 17:31
417 查看
本系列文章目录:
一:前言及基础
二:推断 FIX、g 的类型
三:实现 Y 组合子
四:实现 Θ 组合子
五:推导装配脑袋的 Fix
也许你我都难以理解,为什么有人对她痴迷疯狂,铭记在心中不说,还要刻在身上:
她让人绞尽脑汁,也琢磨不定!她让人心力憔悴,又百般回味!
她,看似平淡,却深藏玄机!她,貌不惊人,却天下无敌!
她是谁?她就是 Y 组合子:Y = λf.(λx.f (x x)) (λx.f (x x)),不动点组合子中最著名的一个。
小小开场抒情后,开始本文的内容,使用 c# 实现 Y 组合子。
本文继续使用上一篇文章中的类型假定,假定递归函数:
参数为 int;
返回值为 long。
根据前文所述,对于传值调用,应使用 η-展开后的组合子:
这个也称为 Z 组合子,是将 (x x) η-展开为 λv. ((x x) n)) 。
不过我更倾向于另外一种 η-展开形式:即将 Y 组合子中的 f (x x) η-展开为 λn. (f (x x) n)) ,最终得出:
(不知道这个组合子有没有名字,没有的话就姑且称为鹤冲天组合子吧,呵呵!)
进行一次 α-变换 变换(将参数 f 改成 g),得出:
右侧两个高亮部分是相同的,定义一个中间的变量 h:
则 Y 可简化为:
Y 可表示为 Y = g => h(h)。
Y = λg. h(h) 变换一步得 Y g = h(h),再变换一次 Y(g) = h(h),可以看出 h 的返回值即是 Y 的返回值。
前文中已确定 Y 返回值的类型为 Func<int, long>,所以 h 的返回值类型为 Func<int, long>。
再来确定 h 的参数类型,h 调用自身 h(h),因此 h 参数的类型就是 h 的类型,h 参数和 h 是同一类型,都是未确定的。
通过以下这个奇妙的委托,可以来表示自身调用有逻辑:
借助 SelfApplicable<TResult> ,h 类型可表示为 SelfApplicable<Func<int, long>> 。
根据 h 的定义
由本系列第一篇文章中总结出的小规律,可写出 lambda表达式如下:
说明:如果前面 Y 没有展开的话,你得出将是:
这样最终得出的 Y 可以编译通过,但运行时会出现死循环,最终导致 StackOverflowException。
可写出 Y组合子的实现代码:
与之等同的方法为:
Y 组合子已实现,下面我们写出几个常用音频函数。
阶乘的单步函数:
斐波那契数列求值单步函数:
使用时就方便多了:
不过,本系列还没有结束:在msdn一篇文章中,我看到这样一种写法:
看上去比本文中的实现要简单方便,怎么得出的?我会在下一篇文章中告诉大家,敬请期待!
附:相关资源:
The Why of Y:http://www.dreamsongs.com/Files/WhyOfY.pdf
数学纹身:http://www.thedistractionnetwork.com/topics/math-and-science/math-tattoos/
图形化 lambda 计算器:http://joerg.endrullis.de/lambdaCalculator.html
一:前言及基础
二:推断 FIX、g 的类型
三:实现 Y 组合子
四:实现 Θ 组合子
五:推导装配脑袋的 Fix
也许你我都难以理解,为什么有人对她痴迷疯狂,铭记在心中不说,还要刻在身上:
她让人绞尽脑汁,也琢磨不定!她让人心力憔悴,又百般回味!
她,看似平淡,却深藏玄机!她,貌不惊人,却天下无敌!
她是谁?她就是 Y 组合子:Y = λf.(λx.f (x x)) (λx.f (x x)),不动点组合子中最著名的一个。
小小开场抒情后,开始本文的内容,使用 c# 实现 Y 组合子。
本文继续使用上一篇文章中的类型假定,假定递归函数:
参数为 int;
返回值为 long。
实现 Y 组合子算法
Y 组合子 η-展开
Y 组合算子的定义:1 | Y = λf.(λx.f(x x)) (λx.f(x x)) |
1 | Y = λf.(λx.f(λv.((x x)v))) (λx.f(λv.((x x)v))) |
不过我更倾向于另外一种 η-展开形式:即将 Y 组合子中的 f (x x) η-展开为 λn. (f (x x) n)) ,最终得出:
1 | Y = λf.(λx.λn.(f(x x)n))) (λx.λn.(f(x x)n))) |
进行一次 α-变换 变换(将参数 f 改成 g),得出:
1 | Y = λg.(λx.λn.(g (x x) n))) (λx.λn.(g(x x)n))) |
简化 Y 组合子
仔细观察展开后的 Y,会发现:1 | Y = λg.(λx.λn.(g(x x)n))) (λx.λn.(g(x x)n))) |
1 | h = λx.λn.(g(x x)n) |
1 2 | Y = λg.h h Y = λg.h(h) |
h 类型确定及实现
先来确定 h 的类型。Y = λg. h(h) 变换一步得 Y g = h(h),再变换一次 Y(g) = h(h),可以看出 h 的返回值即是 Y 的返回值。
前文中已确定 Y 返回值的类型为 Func<int, long>,所以 h 的返回值类型为 Func<int, long>。
再来确定 h 的参数类型,h 调用自身 h(h),因此 h 参数的类型就是 h 的类型,h 参数和 h 是同一类型,都是未确定的。
通过以下这个奇妙的委托,可以来表示自身调用有逻辑:
1 | delegate TResult SelfApplicable<TResult>(SelfApplicable<TResult> self); |
根据 h 的定义
1 2 | h = λx.λn.(g(x x)n) h = λx.λn.(g(x(x))(n)) |
1 | SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n); |
1 | SelfApplicable<Func<int, long>> h = x => g(x(x)); |
Y 组合子实现
综合以上两小节的结论:1 2 | Y = λg.h(h) SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n); |
1 2 3 4 | Func<Func<Func<int, long>, Func<int, long>>, Func<int, long>> Y = g => { SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n); return h(h); }; |
1 2 3 4 | public static Func<int, long> Y(Func<Func<int, long>, Func<int, long>> g) { SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n); return h(h); } |
单步函数
根据上文确定出的 g 类型,可以写出:阶乘的单步函数:
1 | Func<Func<int, long>, Func<int, long>> g = f => n => n == 0 ? 1 : n * f(n - 1); |
1 | Func<Func<int, long>, Func<int, long>> g = f => n => n < 2 ? n : f(n - 1) + f(n - 2); |
进行阶乘计算
综合以上代码:1 2 3 4 | delegate TResult SelfApplicable<TResult>(SelfApplicable<TResult> self); static void Main(string[] args) { Func<Func<Func<int, long>, Func<int, long>>, Func<int, long>> Y = g => { SelfApplicable<Func<int, long>> h = x => n => g(x(x))(n); return h(h); }; Func<int, long> fact = Y(f => n => n == 0 ? 1 : n * f(n - 1)); long result = fact(5); // 结果为 120; } |
对 Y 组合子进行封装
考虑到 Y 的复杂度及重用,可以把相关代码封装成如下:1 2 3 4 | public static class YCombitator { delegate TResult SelfApplicable<TResult>(SelfApplicable<TResult> self); public static Func<TInput, TResult> Fix<TInput, TResult>(Func<Func<TInput, TResult>, Func<TInput, TResult>> g) { SelfApplicable<Func<TInput, TResult>> h = x => n => g(x(x))(n); return h(h); } } |
1 2 3 4 | var factorial = YCombitator.Fix<int, int>(f => n => n == 0 ? 1 : n * f(n - 1)); var result1 = factorial(5); // 120 var fibonacci = YCombitator.Fix<int, int>(f => n => n < 2 ? n : f(n - 1) + f(n - 2)); var result2 = fibonacci(5); // 5 |
总结
通过本文及前面两篇文章的努力,终于使用 c# 实现 Y 组合子,达到了使用 lambda 构造递归函数的目的。不过,本系列还没有结束:在msdn一篇文章中,我看到这样一种写法:
1 2 3 4 | public class Program{ delegate T SelfApplicable<T>(SelfApplicable<T> self); static void Main(string[] args) { SelfApplicable<Func<Func<Func<int,int>, Func<int,int>>, Func<int,int>>> Y = y => f => x => f(y(y)(f))(x); Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>> Fix = Y(Y); Func<Func<int,int>, Func<int,int>> F = fac => x => x == 0 ? 1 : x * fac(x-1); Func<int,int> factorial = Fix(F); var result = factorial(5); // 120 } } |
附:相关资源:
The Why of Y:http://www.dreamsongs.com/Files/WhyOfY.pdf
数学纹身:http://www.thedistractionnetwork.com/topics/math-and-science/math-tattoos/
图形化 lambda 计算器:http://joerg.endrullis.de/lambdaCalculator.html
相关文章推荐
- 使用 Lambda 表达式编写递归四:实现 Θ 组合子
- 使用 Lambda 表达式编写递归五:推导装配脑袋的 Fix
- 使用 Lambda 表达式编写递归一:前言及基础
- 使用 Lambda 表达式编写递归二:推断 FIX、g 的类型
- 【C语言】【面试题】【笔试题】编写一个函数实现n^k,使用递归实现
- 编写一个函数实现n^k,使用递归实现
- 委托、Lambda表达式、事件系列06,使用Action实现观察者模式,体验委托和事件的区别
- 编写一个函数实现n^k,使用递归实现
- 编写一个函数实现n^k,使用递归实现
- .编写一个函数实现n^k,使用递归实现
- 一个使用TBB Lambda 表达式实现并行执行的例子(转)
- 【C语言】编写一个函数实现n^k,使用递归实现
- 使用递归下降算法分析数学表达式 -- 基于堆栈的计算器实现算法
- 编写高质量代码改善C#程序的157个建议——建议37:使用Lambda表达式代替方法和匿名方法
- //4. 编写一个函数reverse_string(char * string)(递归实现) //实现:将参数字符串中的字符反向排列。 //要求:不能使用C函数库中的字符串操作函数。
- 编写一个函数实现n^k,使用递归实现
- 编写一个函数实现n^k,使用递归实现
- easyui datagrid remoteSort的实现 Controllers编写动态的Lambda表达式 IQueryable OrderBy扩展
- 2.编写一个函数实现n^k,使用递归实现
- 编写一个函数实现n^k,使用递归实现