问:上一回,你在最后曾提到“抽象性不足”,这话怎么说?

答:试想,如果现在需要实现一个其它的递归(比如:Fibonacci),就必须把之前的模式从头套一遍,然后通过fib_maker(fib_maker)来返回一个fib函数。可见,这个产生递归过程的“接口”让用户相当不舒服。

问:嗯,fib_maker(fib_maker)这种形式看起来的确不怎么舒服,那又如何对其进行抽象,以得到更好的接口呢?

答:这里,有两条路可以走。其一,就是对fact_maker(fact_maker)进一步抽象,便可得到传说中的Y组合子;其二,则是否定之前所为,走一条能反应Y组合子本质的路。二选一,你走那条?

问:???...

答:那就走第二条路吧,至于如何抽象出Y组合子,放到第(四)章谈。 在开始今天的讨论之前,我们先来定义一些委托类型,以方便后文的表达:

//C#
delegate T OuroborosFunc<T>(OuroborosFunc<T> self);
class Y<T, TResult>
{
public delegate TResult Func1(T p);
public delegate Func1 Func2(Func1 f1);
public delegate Func1 Func3(Func2 f2);
... ...
}

这样就可以得到(==表示类型等价,并且在下文使用时省略Y<int,int>限定):

//C#伪码
Func1 == Func<int,int>
Func2 == Func<Func1,Func1> == Func<Func<int,int>,Func<int,int>>
Func3 == Func<Func2,Func1> == Func<Func<Func<int,int>,Func<int,int>>,Func<int,int>>

OK,让我们回忆一下上一章的讨论:

//C#
OuroborosFunc<Func<int, int>> fact_maker =
self => x => x == 0 ? 1 : x * self(self)(x - 1);

为了传递fact_maker给self,我们在Lambda的主体内做了让步——以self(self)的形式来表达fact递归。试想,如果我们不打算传入fact_maker,而是直接传入一个能表达递归的Lambda(类型为Func<int,int>,即Func1),且同时能返回这个Lambda,那么,将得到如下结果:

//C#
Func2 fact_seed = fact => x => x == 0 ? 1 : x * fact(x - 1);

对此,我们来做一个形式化的描述,令F为fact_seed,f为那个表达递归的Lambda,则:F(f) = f, 也就是说f是F的一个不动点。所谓函数的不动点,即函数作用于之得到的仍是其本身的那个点,如:若F(x)=x*x, 则1则是F的一个不动点,因为F(1)=1. 到这里,大家应该可以看出来了,我们期望得到的那个匿名递归fact,其实正是fact_seed的不动点。再试想,如果我们有这么一个函数Fix,它的作用就是求某个函数的不动点,那么,所要求的fact即为Fix(fact_seed).  终于,理想的接口产生了,那就是只要把诸如fact_seed这样的Lambda交给Fix,得到的不动点就是问题的解。

//C#
Func1 fact = Fix(fact_seed);
//或者
Func1 fact = Fix(fact => x => x == 0 ? 1 : x * fact(x - 1));

问:那这个Fix到底是什么呢?

答:根据以上伪码,Fix的类型已然昭昭:Func3,再根据其功能,似乎应该是这样一个高阶函数:

//C#伪码
Func3 Fix = f=>(f的不动点);

而f的不动点怎么表示呢?Fix(f),这样代入上式其实什么也没有做。既然Fix(f)是f的不动点,那Fix(f)=f(Fix(f))=f(f(Fix(f)))...都应该是f的不动点。于是得到(当然也可以取f(f(Fix(f))),我没测试过,应该没问题):

//C#
Func3 Fix = f=>f(Fix(f));

这里需要稍加甄别,对于lazy求值的函数语言来说,上式的定义的确没问题,但是C#是eager求值的,所以Fix(some_f)表答的意思是——将f应用于Fix(some_f)的求值,而Fix(some_f)的求值势必导致新一轮的求值,直至堆栈溢出,可以这样修正这个问题:

//C#
Func3 Fix = f=>f(x=>Fix(f)(x));

现在可以来测试一下结果了:

//C#
int result = Fix(fact => x => x == 0 ? 1 : x * fact(x - 1))(5);
Console.WriteLine(result); //120

依然非常棒,得到了正确的结果。

问:慢着...这里似乎有问题啊,我们的目的是完成一个匿名的递归,那个fact_seed的确是可以做到匿名,但是Fix却是一个普通的递归啊!

答:Good Question! 那你想想应该怎么解决这个问题呢?

问:嗯,让我好好想想,再听你下回分解吧。

答:待续...

大到可以小说的Y组合子(二)的更多相关文章

  1. 大到可以小说的Y组合子(一)

    问:上回乱扯淡了一通,这回该讲正题了吧. 答:OK. 先来列举一些我参考过,并从中受到启发的文章. (1.)老赵的一篇文章:使用Lambda表达式编写递归函数 (2.)装配脑袋的两篇文章:VS2008 ...

  2. 大到可以小说的Y组合子(三)

    答:关于Fix的问题你fix了吗? 问:慢着,让我想想,上次留下个什么问题来着?是说我们有了一个求不动点的函数Fix,但Fix却是显式递归的,是吧? 答:有劳你还记的这个问题. 问:Fix的参与背离了 ...

  3. 大到可以小说的Y组合子(零)

    问:啊!我想要一个匿名的递归… 答:Y(音同Why)… … … 问:作为一位命令式语言的使用者,为什么会突然折腾起Y组合子呢? 答:的确,这事儿要从很久以前的几次搁浅开始说起…上学的时候,从来没有接触 ...

  4. Y组合子

    Y组合子 Y组合子的用处 作者:王霄池链接:https://www.zhihu.com/question/21099081/answer/18830200来源:知乎著作权归作者所有.商业转载请联系作者 ...

  5. Lambda演算 - 简述Y组合子的作用

    Y组合子:\f.(\x.f(xx))(\x.f(xx)),接受一个函数,返回一个高阶函数 Y组合子用于生成匿名递归函数. 什么叫匿名递归函数,考虑以下C语言递归函数 int sum(int n) { ...

  6. Racket中使用Y组合子

    关于Y组合子,网上已经介绍很多了,其作用主要是解决匿名lambda的递归调用自己. 首先我们来看直观的递归lambda定义, 假设要定义阶乘的lambda表达,C#中需要这么定义 Func<in ...

  7. 简单易懂的程序语言入门小册子(4):基于文本替换的解释器,递归,如何构造递归函数,Y组合子

    递归.哦,递归. 递归在计算机科学中的重要性不言而喻. 递归就像女人,即令人烦恼,又无法抛弃. 先上个例子,这个例子里的函数double输入一个非负整数$n$,输出$2n$. \[ {double} ...

  8. [学习] 从 函数式编程 到 lambda演算 到 函数的本质 到 组合子逻辑

    函数式编程 阮一峰 <函数式编程初探>,阮一峰是<黑客与画家>的译者. wiki <函数编程语言> 一本好书,<计算机程序的构造与解释>有讲到schem ...

  9. 面向组合子设计Coder

    面向组合子 面向组合子(Combanitor-Oriented),是最近帮我打开新世界大门的一种pattern.缘起haskell,又见monad与ParseC,终于ajoo前辈的几篇文章. 自去年9 ...

随机推荐

  1. C#构造函数的 "继承" 问题

    首先说明下 之所以用 双引号 是因为构造函数是没有继承的 派生类默认会调用基类的无参数构造函数 比如: public class A         { public A()         { Co ...

  2. webform开发经验(一):Asp.Net获取Checkbox选中的值

    webform中获取repeat控件列表下的checkbox选中的值: 码农上代码: public static string getSelectedIDs(Repeater Rpt_) { stri ...

  3. Tomcat6+nginx集群,达到负载均衡和session复制

    nginx+tomcat做web项目集群,达到负载均衡.故障转移.session复制功能. 1.nginx配置文件见上一篇“nginx配置文件(反向代理+集群+动静分离)” 2.tomcat集群,修改 ...

  4. C#设置按钮三态背景图片

    1.闪电事件,注意添加手动或者点击按钮->属性中的闪电->下面对应各种事件 在上面: private void button1_MouseHover(object sender, Even ...

  5. sqlite编译

    1.下载代码:http://www.sqlite.org/download.html ,windows下下载sqlite-amalgamation-xxx.zip和sqlite-dll-win32-x ...

  6. xml解析,练习

    <collection shelf="New Arrivals"><movie title="Enemy Behind">   < ...

  7. python代码风格规范

    类注释模板: :: class AnotherClass: """ 类注释 """ def method(self, arg1, arg2, ...

  8. vim 删除临时文件

    今天在用Xshell连接到CentOS后 使用vim 编辑文档 因为中途有事  临时关闭 并没有保存 再一次打开时 vim 提示要恢复 , 但是每次打开文件后到要恢复,于是找到了以下办法 和vim工作 ...

  9. A51汇编器的解释

    A51汇编器是运行于IBM PC系列及其兼容机上的交叉汇编软件,其主要功能是将MCS-51系列单片机汇编语言源程序翻译成符合Intel目标文件格式的可再定位的目标代码,经过L51连接器的连接和装配,产 ...

  10. keil c51中C程序的启动过程

    汇编是从org 0000h开始启动,那么keil c51是如何启动main()函数的?keil c51有一个启动程序startup.a51,它总是和c程序一起编译和链接.下面看看它和main()函数是 ...