首页 > 其他 > 详细

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

时间:2015-02-01 23:16:20      阅读:312      评论:0      收藏:0      [点我收藏+]

问:上回乱扯淡了一通,这回该讲正题了吧。

答:OK. 先来列举一些我参考过,并从中受到启发的文章。

(1.)老赵的一篇文章:使用Lambda表达式编写递归函数

(2.)装配脑袋的两篇文章:VS2008亮点:用Lambda表达式进行函数式编程用Lambda表达式进行函数式编程(续):用C#实现Y组合子

(3.)Y组合子的推导过程(用Scheme推导),这里的“推导”并不是数学意义上上的推导证明,而是说如何一步步引导构想出Y来的,值得一看。也许从某种程度反应出了当年Y是如何被发明的。

[还有一些文章一时半会儿也找不到了,哪天记起来再过来补上]

 

      本文决定用C#来展示列子,因为C#对普通函数和Lambda表达式都支持得非常好,方便进行对比。同时,会展示其它语言与之不同之处(如js,scheme是动态类型语言,因此实现Y简单不少;F#应该算同门师兄弟了,除了语法更间接之外,也有一个不大不小的区别;C++是一个更加静态的语言,实现起来难度更大些,需要一个不大不小的技巧,这些会在后面章节中展示)。

      还是从那个古老但有效的例子开始吧——factorial。这是一个普通的递归函数:

//C#
int Fact(int x) { return x == 0 ? 1 : x * Fact(x - 1); }
所谓递归,就是在函数体内调用函数本身,然而Lambda表达式(下文简称Lambda)是一个匿名函数,又能通过什么去调用自己呢?我曾看到网友给出过这样一个啼笑皆非的方案:
//C#
Func<int, int> fact = null;
fact = x => x == 0 ? 1 : x * fact(x - 1);

对应到F#中就是这样的,这就跟前面C#中的函数颇为相似了:

//F#
let rec fact = fun x -> if(x=0) then 1 else x * fact(x-1)

可是这并没有实现所谓的匿名递归,因为你的确为了调用自身,而给自身取了一个名字“fact”,且它被上下文依赖。所以我们来规定来定义一个匿名递归Lambda的“衡量标准”:仍然以factorial函数为例,说我需要这么一个表达式(其中的所有参量不被上下文依赖,即构成闭包),将其代入到以下代码的(*)处,可以计算得到参数(5)的阶乘值(120)。

//C#伪码 
int result = (*)(5);

那不为自身命名,又如何调用之呢?请看下面这两行伪码,其中T?代表一种未知类型:

//C#伪码
T? fact_maker = self =>
x => x == 0 ? 1 : x * self(x - 1
);
int result = fact_maker(fact_maker)(5);

我们期望的是,把这个Lambda自身通过self参量传递给Lambda自己(就像fact_maker(fact_maker)那样样),以此来返回以上伪码的红色部分(即类型为Func<int,int>的fact函数)。但是这里有个矛盾——我们既希望self能接受fact_maker(类型为Func<Tself,Func<int,int>>,其中Tself代表self的类型),又希望self在Lambda内部扮演fact的角色(类型为Func<int,int>)。所以,这样是行不通的。既然我们希望,fact是self的返回值,不如这样试试:

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

这样来分析一下,self的类型应该是Func<Tself,Func<int,int>>,self(self)的返回类型正好是Func<int,int>,同时fact_maker也是这个类型,可以顺利地被self传递,一切都很和谐。那这Tself该怎么表达呢?它接受自己,且返回Func<int,int>,因此我定义这么一个委托(Ouroboros意思是咬着尾巴构成一个环的蛇):

//C#
delegate T OuroborosFunc<T>(OroborosFunc<T> self);

这样OuroborosFunc<Func<int,int>>正是我们需要的self类型。到这里,可以测试一下以上的构想,看看是否能良好工作。

//C#
OuroborosFunc<Func<int, int>> fact_maker = self => x => x == 0 ? 1 : x * self(self)(x - 1);
int result = fact_maker(fact_maker)(5);
Console.WriteLine(result);  //120

非常棒,输出了正确的结果。如果你愿意,可以不需要这个fact_maker,而是直接把这个Lambda应用到另一个同样的Lambda身上,不过需要注意显式声明类型,因为C#不允许Lambda自动类型推断,就像这样:

//C#       
 int result = ((OuroborosFunc<Func<int, int>>)
               (self => x => x == 0 ? 1 : x * self(self)(x - 1)))
                   ((OuroborosFunc<Func<int, int>>)
                   (self => x => x == 0 ? 1 : x * self(self)(x - 1)))(5);
Console.WriteLine(result);  //120

嗯,也不错,虽然丑陋了一点,但毕竟还是满足了之前对匿名递归的定义。

问:可是…还是没有讲到Y组合子…

答:匿名递归是初级地完成了,但是毫无复用性,且不朽的丑陋。离Y组合子还有不少的路要走,请耐性待续…

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

原文:http://www.cnblogs.com/coeuschen/p/4266453.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!