代码改变世界

反驳 老赵 之 “伪”递归

2009-08-31 21:34  鹤冲天  阅读(5321)  评论(45编辑  收藏  举报

 今天看了老赵的随笔《使用Lambda表达式编写递归函数》,老赵给其中两行代码下了“伪递归”的定论,我非常不满,今写文反驳之。

 先说下老赵这篇文章的由来,我之前也写过一篇和递归有关的随笔《由Fibonacci数列引出“委托扩展”及“递推递归委托”》,里面给出了这样的一个递归定义(以下称为代码一): 

    public static Func<intint> Fibonacci = n => n > 1 ? Fibonacci(n - 1+ Fibonacci(n - 2) : n;

 是计算Fibonacci数列的,注意上这句代码中用了“static”,可以编译通过,绝对没有问题!老赵回复中说不用static无法编译通过,于是我又给出了以下代码(以下称为代码二): 

    Func<intint> Fibonacci = null;
    Fibonacci 
= n => n > 1 ? Fibonacci(n - 1+ Fibonacci(n - 2) : n;

 代码二就是老赵随笔中开始处的那两行被称为“伪”递归代码,所说的朋友自然就是我了!

 

 首先老赵随笔只引用了代码二(是在回复中给出的),而代码一(正文中的)却根本没有提及,不免有断章取义的嫌疑。接着老赵又给我的代码戴上了“伪”递归的帽子...

 于是我奋起反驳,以螳臂当车之力... 

 我先把我的代码改进一下,以便更加有力度(以下称为代码三):

1     //Fibonacci数组
2     public static readonly Func<intint> Fibonacci = n => n > 1 ? Fibonacci(n - 1+ Fibonacci(n - 2) : n;
3     //阶乘
4     public static readonly Func<intint> fac = x => x <= 1 ? 1 : x * fac(x - 1);

 注意两点,必须加上static才能编译;加上readonly防止窜改!第4行的阶乘的代码相对简单些,我就用这个作为例子吧!

 

 先把老赵用说明我的方法有问题的例子拿出来,方便大家阅读:

1     Func<intint> fac = null;
2     fac = x => x <= 1 ? 1 : x * fac(x - 1);
3     Console.WriteLine(fac(5)); // 120;
4 
5     Func<intint> facAlias = fac;
6     fac = x => x;
7     Console.WriteLine(facAlias(5)); // 20

 这段代码只能说明代码二存在问题,对我改进后的带readonly的代码三就不起作用了:readonly说明fac的是专用的,是不允许修改的。

 再说下“伪”递归!最初没看太明白到底什么是“伪”递归,反复看了几次,再加上回复中的解释,大体有点明白了。

 老赵在回复中说“...递归的定义就是自己调用自己,不是执行一个委托对象。”,我想“自己调用自己”这个没问题,不能执行一个委托对象这个不恰当。百度百科中这样定义递归:“一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法”,过程和方法我想不应该仅仅限于“过程”和“方法”这两个形式,匿名方法、lambda也应该在其中,如果我们坚持只能是过程或方法,就有点固步自封了,毕竟世界是飞速发展的。

 RednaxelaFX则在回复中用绑定时间来解释,如下:

1     Func<intint> fac = null;              // fac的绑定(1)
2     fac = x => x <= 1 ? 1 : x * fac(x - 1); // fac的绑定(2);lambda里的fac暂时还在用绑定(1)
3     fac(5); // 使用fac的绑定(2)

 也是针对代码二的,我们来看下代码三中第4行反编译后是怎样的:

 1     internal static class Program
 2     {
 3         // Fields
 4         public static readonly Func<intint> fac;
 5 
 6         // Methods
 7         static Program()
 8         {
 9             fac = delegate (int x) {
10                 return (x <= 1? ((void1) : ((void) (x * fac(x - 1)));
11             };
12         }
13     }

 第9、10行,fac是在静态构造函数中完成绑定的。所以在fac(5)时,fac早已绑定完成。

 再看RednaxelaFX的最后的解释“在所谓‘真正的递归’中,整个表达式的所有值都是‘已绑定’的;在所谓‘伪递归’中,例子里的fac = null可以理解为fac的一种‘未绑定’状态,在定义fac的真正内容时引用的也是‘未绑定’状态,所以‘不纯’”,在这里也不成立了!

 老赵在第二部分“把自己传给自己吧”中,带领大家完成了一个复杂的实现,并给出了一个辅助方法(前面已引用):

1     static Func<T, TResult> Make<T, TResult>(SelfApplicable<T, TResult> self)
2     {
3         return x => self(self, x);
4     }

 Make方法还是要传入一个委托,self(self,x),还是委托调用委托,与老赵自己的观点“...递归的定义就是自己调用自己,不是执行一个委托对象。”是自相矛盾的!self不也是一个委托对象吗?

 再来看一下代码的简洁性和易读性:  

1     //鹤冲天
2     static readonly Func<intint> fac = x => x <= 1 ? 1 : x * fac(x - 1);
3     //老赵
4     static Func<T, TResult> Make<T, TResult>(SelfApplicable<T, TResult> self)
5     {
6         return x => self(self, x);
7     }
8     var fac = Make<intint>((f, x) => x <= 1 ? 1 : x * f(f, x - 1));9 

 

 大家对比吧!



 再说下我的方法: 

1     public static readonly Func<intint> fac = x => x <= 1 ? 1 : x * fac(x - 1);

 说投机取巧也罢,偷工减料也罢,最终我是借助c#的特性,用最简单、最明了的方式完成了递归! 

 把这样一个方法说成“伪”递归,是我不能接受的,这是明显排斥的!

 好了,就说这些,大家评判吧!