大到可以小说的Y组合子(三)
答:关于Fix的问题你fix了吗?
问:慢着,让我想想,上次留下个什么问题来着?是说我们有了一个求不动点的函数Fix,但Fix却是显式递归的,是吧?
答:有劳你还记的这个问题。
问:Fix的参与背离了匿名递归的定义,所以…所以…我们被Fix给坑了?
答:当然不是。你还记的第(一)章我们讨论过什么吗?
问:记的,我们把一个显式递归的Fact变成了一个匿名递归的结构。
答:很好,让我们再造一次轮子。
问:哦!我明白了,是用与上次类似的方法,把Fix写成一个匿名递归的Lambda。
答:就是这个意思,如此便可用这个Lambda来获得不动点了。那就动手吧,让我们一路与Fact对比:
//C#
//Func3 Fix = f=>f(Fix(x=>f(x)))
//写成函数的形式为:
Func1 Fix(Func2 f) { return (Func1)(f(x =>Fix(f)(x))); }
//对比Fact
int Fact(int x) { return x == 0 ? 1 : x * Fact(x - 1); }
//C#
//为了把自己传给自己构造fix_maker
OuroborosFunc<Func3> fix_maker =
self => f => f(x => self(self)(f)(x));
//对比fact_maker
OuroborosFunc<Func<int, int>> fact_maker =
self => x => x == 0 ? 1 : x * self(self)(x - 1);
//C#
//自我调用产生Fix
Func3 Fix = ((Func<OuroborosFunc<Func3>, Func3>)(s => s(s)))
(self => f => f(x => self(self)(f)(x)));
//对比自我调用产生的Fact
Func<int, int> Fact =
((Func<OuroborosFunc<Func<int, int>>, Func<int, int>>)(s => s(s)))
(self => x => x == 0 ? 1 : x * self(self)(x - 1));
观察最后产生的Fix,发现我们已经摆脱了显式的递归调用,且得到了一个可复用的不动点“生成器”。这里插一句题外话,我之前按照上述思路得到了Fix,后来才发现,原来这就是阿兰•图灵当年发现的Θ组合子(当然,我得到这个组合子要容易得多,因为图灵的年代根本没有计算机,更别说程序语言了)。我相信这两个组合子是完全等价的,下一章我们将以非形式化的方法来理解这一点。
问:那么,是时候揭开Y组合子的面纱了吧。
答:让子弹再飞一会儿。在此之前,“插播”一个小问题:如果递归函数的输入参数不止一个,该怎么办呢?例如用辗转相除法求最大公约数:
//C#
//GCD的显式递归
int GCD(int m, int n) { return n == 0 ? m : GCD(n, m % n); }
问:让我想想…嗯…那就要对之前定义的Y<T,TResult>做一些改变,写成这样:
//C#
class Y<T1, T2, TResult>
{
public delegate TResult Func1(T1 p1, T2 p2);
public delegate Func1 Func2(Func1 f1);
public delegate Func1 Func3(Func2 f2);
... ...
}
然后,对之前的Fix改写成这样:
//C#
Func3 Fix = ((Func<OuroborosFunc<Func3>, Func3>)(s => s(s)))
(self => f => f((x, y) => self(self)(f)(x, y)));
如此便可用这个Fix得到gcd_seed的不动点,即GCD递归函数:
//C#
Y<int, int, int>.Func2 gcd_seed = gcd => (m, n) => n == 0 ? m : gcd(n, m % n);
Console.WriteLine(Y<int, int, int>.Fix(gcd_seed)(24, 15)); //3
答:很好,这是一个方案,但不是一个通用方案。如果有三个输入参数的递归呢?四个呢?…
问:难道还有更好的方法吗?
答:是的,答案就是Currying(柯里化)。所谓柯里化,就是把接受多个参数的函数变换成接受单一参数的函数,并且返回以柯里化的形式接受剩余参数,最终返回结果的一种技术。(注:我是在独立想到解决方案之后,才知道Currying的存在的哦!)
问:这…讲得有点抽象…
答:那就直接上例子吧。在上述最大公约数的例子中,把GCD柯里化成如下形式:
//C#
Func<int, int> GCD(int m) { return n => n == 0 ? m : GCD(n)(m % n); }
即:高阶函数GCD以m为参数,返回一个能与m求最大公约数的函数,这个函数再受一个参数n,便可返回(m,n)的最大公约数。同时,Y<T, TResult>和Fix无需任何改变,即可完成有两个输入参数的递归:
//C#
Y<int, Func<int, int>>.Func2 gcd_seed =
gcd => m => n => n == 0 ? m : gcd(n)(m % n);
Console.WriteLine(Y<int, Func<int, int>>.Fix(gcd_seed)(24)(15)); //3
依此看来,两个参数的Y<T, TResult>就足以满足任何递归的需求了,美哉!
问:然是美哉!
答:就在下章,静候Y组合子的“粉墨登场”。待续…
大到可以小说的Y组合子(三)的更多相关文章
- 大到可以小说的Y组合子(一)
问:上回乱扯淡了一通,这回该讲正题了吧. 答:OK. 先来列举一些我参考过,并从中受到启发的文章. (1.)老赵的一篇文章:使用Lambda表达式编写递归函数 (2.)装配脑袋的两篇文章:VS2008 ...
- 大到可以小说的Y组合子(二)
问:上一回,你在最后曾提到"抽象性不足",这话怎么说? 答:试想,如果现在需要实现一个其它的递归(比如:Fibonacci),就必须把之前的模式从头套一遍,然后通过fib_make ...
- 大到可以小说的Y组合子(零)
问:啊!我想要一个匿名的递归… 答:Y(音同Why)… … … 问:作为一位命令式语言的使用者,为什么会突然折腾起Y组合子呢? 答:的确,这事儿要从很久以前的几次搁浅开始说起…上学的时候,从来没有接触 ...
- Y组合子
Y组合子 Y组合子的用处 作者:王霄池链接:https://www.zhihu.com/question/21099081/answer/18830200来源:知乎著作权归作者所有.商业转载请联系作者 ...
- Lambda演算 - 简述Y组合子的作用
Y组合子:\f.(\x.f(xx))(\x.f(xx)),接受一个函数,返回一个高阶函数 Y组合子用于生成匿名递归函数. 什么叫匿名递归函数,考虑以下C语言递归函数 int sum(int n) { ...
- Racket中使用Y组合子
关于Y组合子,网上已经介绍很多了,其作用主要是解决匿名lambda的递归调用自己. 首先我们来看直观的递归lambda定义, 假设要定义阶乘的lambda表达,C#中需要这么定义 Func<in ...
- 简单易懂的程序语言入门小册子(4):基于文本替换的解释器,递归,如何构造递归函数,Y组合子
递归.哦,递归. 递归在计算机科学中的重要性不言而喻. 递归就像女人,即令人烦恼,又无法抛弃. 先上个例子,这个例子里的函数double输入一个非负整数$n$,输出$2n$. \[ {double} ...
- [学习] 从 函数式编程 到 lambda演算 到 函数的本质 到 组合子逻辑
函数式编程 阮一峰 <函数式编程初探>,阮一峰是<黑客与画家>的译者. wiki <函数编程语言> 一本好书,<计算机程序的构造与解释>有讲到schem ...
- 面向组合子设计Coder
面向组合子 面向组合子(Combanitor-Oriented),是最近帮我打开新世界大门的一种pattern.缘起haskell,又见monad与ParseC,终于ajoo前辈的几篇文章. 自去年9 ...
随机推荐
- JavaScript函数 bind call apply区别
1. apply calll 在JavaScript中 call 和 apply 都是为了改变某个函数运行时上下文而存在的, 换句话说就是为了改变函数内部的this的指向. 这里我们有一个新的对象 b ...
- Hacker(22)----解除系统中的密码
Win7系统中,用户可以设置BIOS密码以及系统登录密码,这些密码都有各自的用途.但对黑客而言,完全可以采用不同的方式绕过这些密码.下面介绍黑客如如何接触这些密码. 一.解除BIOS密码 BIOS密码 ...
- mysql三种binlog日志的理解
最近,一直在纠结要不要改数据库binlog的日志格式,原先用的是row格式,导致数据库binlog日志较大,磁盘空间本来也不是很大,所以就想看看能不能改变binlog日志.在该binlog日志之前,先 ...
- LNMP优化
LNMP优化 LNMP优化从系统安全,系统资源占用率,及web服务并发负载这三个方面体现,并 且主要体现在web服务并发负载这一方面. 1:首先进行linux优化加固 Linux ...
- oracle启动,提示“LRM-00109: could not open parameter file”
转载自 http://blog.sina.com.cn/s/blog_53e731b70101liku.html oracle启动,提示“LRM-00109: could not open par ...
- python字符串的encode和decode
原文 decode的作用是将其他编码的字符串转换成unicode编码. str1.decode('gb2312') #表示将gb2312编码的字符串转换成unicode编码 encode的作用是将un ...
- HEAP[xxx.exe]:Invalid Address specified to RtlValidateHeap 错误的解决方法总结
一.情况 抽象出问题是这样的: class DLL_API1 A { func() { vector vec; B b; b.func(vec); return TRUE; } } 其中B是另一个导出 ...
- MyISAM 存储引擎
在MYSQL 5.1 以及之前的版本,MyISAM 是默认的存储引擎.MyISAM 提供了大量的特性,包括全文索引,压缩,空间函数(gis)等,但是MyISAM不支持事务和行级锁,而且有一个毫无疑问的 ...
- rman全库恢复到不同主机,不同实例名,不同目录下
一.配置目标主机的ip.hostname及与源端主机的连通性 1.配置目标主机IP 使用图形界面配置IP: administration----network---修改IP(指定静态IP) deact ...
- 阿铭linux笔记
2015-09-06虚拟机网络设置.wmv: curl 获取在命令行显示的网页 dhclient 分配ip地址 ifdown eth0 关闭网卡eth0 ifup eh0 ...