码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-02-02 22:54:18      阅读:246      评论:0      收藏:0      [点我收藏+]

标签:

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

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

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

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

问:???... 

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

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

这样就可以得到(==表示类型等价,并且在下文使用时省略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>),且同时能返回这个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, 则0则是F的一个不动点,因为F(0)=0. 到这里,大家应该可以看出来了,我们期望得到的那个匿名递归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组合子(二)

标签:

原文地址:http://www.cnblogs.com/coeuschen/p/4268178.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!