递归函数,堆栈溢出和Y组合器

时间:2011-12-02 07:10:09

标签: c# recursion functional-programming stack-overflow y-combinator

我有一个递归函数(在C#中)我需要调用大约8亿次;这显然通常会在大约第900次呼叫后导致堆栈溢出。我已经将它踢出了多个循环,但递归模式更容易,维护更清晰。

我正在考虑使用y-combinator实现递归函数,就像我正在阅读和看到的那样,应该解决堆栈溢出问题,并修复多个嵌套循环。

有没有人有使用y-combinator的经验?我还会陷入堆栈溢出吗?

以一个阶乘的简单例子为例。大多数数字大于5,000的因子将导致堆栈溢出。如果我在那个场景中正确使用了y-combinator,它会修复堆栈溢出吗?

实现起来似乎并不重要,所以我想在开发工作/资源实施和学习y-combinator之前确认它。

4 个答案:

答案 0 :(得分:6)

不,使用Y-combinator无助于您的情况。如果你需要做8亿次,你可以(a)递归,或(b)循环(或者我认为(c)写入你的函数8亿次)。由于Y-combinator不循环,因此必须递归。

循环是您正在寻找的,除非您使用提供正确尾递归的运行时环境(例如Scheme)。

话虽如此,用您选择的语言从头开始实施Y-combinator是一个很好的练习。

答案 1 :(得分:6)

Y组合器很有用,但我认为你真的想要尾递归优化,它消除了对尾递归函数的堆栈使用。尾递归只有在调用者立即返回每个递归调用的结果时才有可能,并且在调用之后永远不会有任何额外的计算。不幸的是,C#不支持尾递归优化,但是你可以使用trampoline 来模拟它,这允许从尾递归方法到trampoline包装方法的简单转换。

// Tail
int factorial( int n ) { return factorialTail( n, 1, 1 ); }
int factorialTail( int n, int i, int acc ) {
  if ( n < i ) return acc;
  else return factorialTail( n, i + 1, acc * i );
}

// Trampoline
int factorialTramp( int n ) { 
   var bounce = new Tuple<bool,int,int>(true,1,1);
   while( bounce.Item1 ) bounce = factorialOline( n, bounce.Item2, bounce.Item3 );
   return bounce.Item3;
}
Tuple<bool,int,int> factorialOline( int n, int i, int acc ) {
  if ( n < i ) return new Tuple<bool,int,int>(false,i,acc);
  else return new Tuple<bool,int,int>(true,i + 1,acc * i);
}

答案 2 :(得分:5)

你可以像在Reactive Extension中使用的那样使用蹦床,我试图在我的博客上解释它http://rohiton.net/2011/08/15/trampoline-and-reactive-extensions/

答案 3 :(得分:1)

一种解决方案是将您的函数转换为使用循环和显式上下文数据结构。上下文表示人们通常认为的调用堆栈。您可能会发现my answer to another question about converting to tail recursion很有用。相关步骤为1到5; 6和7特定于该特定功能,而你的声音听起来更复杂。

“简单”替代方案是为每个功能添加一个深度计数器;当它达到某个限制(通过实验确定)时,生成一个新线程以使用新堆栈继续递归。旧线程阻塞等待新线程向其发送结果(或者如果您想要花哨,结果或异常重新提升)。新线程的深度计数器返回0;当它达到限制时,创建第三个线程,依此类推。如果你缓存线程以避免支付线程创建成本的频率超过必要的程度,那么希望你能获得可接受的性能,而不必彻底重构你的程序。