C# 递归函数、堆栈溢出和Y组合器

C# 递归函数、堆栈溢出和Y组合器,c#,recursion,functional-programming,stack-overflow,y-combinator,C#,Recursion,Functional Programming,Stack Overflow,Y Combinator,我有一个递归函数(在C#中),我需要调用大约8亿次;这显然通常会在第900次调用后导致堆栈溢出。我已经把它踢出了多个循环,但是递归模式更容易维护,更干净 我正在考虑使用y组合器实现递归函数,正如我所阅读和看到的那样,它应该可以解决堆栈溢出问题,并修复多个嵌套循环 有人有使用y-combinator的经验吗?我还会被困在堆栈溢出中吗 以一个简单的阶乘为例。大多数大于5000的数字的阶乘将导致堆栈溢出。如果我在该场景中正确使用y组合器,它会修复堆栈溢出吗 它的实现似乎并不琐碎,因此我想在花费开发精力

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

我正在考虑使用y组合器实现递归函数,正如我所阅读和看到的那样,它应该可以解决堆栈溢出问题,并修复多个嵌套循环

有人有使用y-combinator的经验吗?我还会被困在堆栈溢出中吗

以一个简单的阶乘为例。大多数大于5000的数字的阶乘将导致堆栈溢出。如果我在该场景中正确使用y组合器,它会修复堆栈溢出吗


它的实现似乎并不琐碎,因此我想在花费开发精力/资源实施和学习y-combinator之前确认它。

不,使用y-combinator对您的情况没有帮助。如果你需要做8亿次,你可以(a)递归,或者(b)循环(或者我想(c)写8亿次函数调用)。由于Y组合子不循环,因此必须递归

循环就是您要寻找的,除非您使用的是提供适当尾部递归的运行时环境(如Scheme)


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

Y-combinator很有用,但我认为您确实需要尾部递归优化,它消除了对尾部递归函数使用堆栈的情况。只有当调用方立即返回每个递归调用的结果时,才可能进行尾部递归,并且调用后不会进行任何额外的计算。不幸的是,C#不支持尾部递归优化,但是您可以使用蹦床对其进行模拟,蹦床允许从尾部递归方法到蹦床包装方法的简单转换

// 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);
}
//尾部
因式阶乘(因式n){返回因式阶乘(n,1,1);}
int factorialTail(int n,int i,int acc){
如果(n
您可以像在反应式扩展中一样使用蹦床,我在我的博客上尝试过解释它

一个解决方案是将函数转换为使用循环和显式上下文数据结构。上下文表示人们通常认为的调用堆栈。你可能会觉得有用。相关步骤为1至5;6和7是特定于该特定函数的,而您的函数听起来更复杂


不过,“简单”的选择是为每个函数添加一个深度计数器;当它达到某个极限(由实验确定)时,生成一个新线程以使用新堆栈继续递归。旧线程阻塞,等待新线程向其发送结果(或者,如果您想显示,则重新引发结果或异常)。对于新线程,深度计数器返回到0;当它达到极限时,创建第三个线程,依此类推。如果您缓存线程以避免支付线程创建成本的次数超过必要的次数,希望您能够在不必大幅重组程序的情况下获得可接受的性能。

不幸的是,不是,我使用的是C#,并在3个单独的集合之间循环,每个集合生成2到3个内部集合,这需要调用多达8个相同函数的不同版本,它们之间只有小的变化。我设法将它重写为一个函数(仍然有近120行),它确实递归地调用自己,但不使用尾部递归,因此它在第900次调用时轰然倒塌。据我所知,它确实支持自.NET 4.0:和以来正确的尾部递归。当为任何CPU编译并在64位机器上运行程序时,它肯定会进行尾部递归优化-如果这有帮助的话。。。