可以继续使用continuation作为递归的替代吗?

时间:2010-03-17 00:14:04

标签: ruby recursion continuations

以下函数为n = 5,000

生成'堆栈级太深(SystemStackError)'
def factorial(n)
  n == 0 ? 1 : factorial(n -1) * n
end

有没有办法使用continuation / callcc来避免这个错误?

注意:

我知道这可以在没有递归的情况下实现。 e.g。

def factorial2(n)
  (1..n).inject(1) {|result, n| result * n } 
end

3 个答案:

答案 0 :(得分:5)

不确定。延续可以做任何事情!但是,你最终会重新发明两件事之一:循环或函数调用。在我的机器上,默认情况下会进行尾调用优化,使用call / cc进行尾递归会使得到优化。当每个callcc显然捕获整个调用堆栈时,程序很快就会陷入困境。该代码被破坏,这是一个call / cc循环:

def fact( n )
    (n, f, k) = callcc { |k| [ n, 1, k ] }
    if ( n == 0 ) then return f
    else k.call n-1, n*f, k
    end
end

注意:以前我忘记了调用/ cc不只是启动一个连续传递链并且对递归的需要感到困惑,因此下面的注释。

答案 1 :(得分:1)

您可以使用tailcall_optimize :factorial启用尾随呼叫优化,取自here

class Class
  def tailcall_optimize( *methods )
    methods.each do |meth|
      org = instance_method( meth )
      define_method( meth ) do |*args|
        if Thread.current[ meth ]
          throw( :recurse, args )
        else
          Thread.current[ meth ] = org.bind( self )
          result = catch( :done ) do
            loop do
              args = catch( :recurse ) do
                throw( :done, Thread.current[ meth ].call( *args ) )
              end
            end
          end
          Thread.current[ meth ] = nil
          result
        end
      end
    end
  end
end

有关确定是否启用了尾递归的信息,请参阅this interesting post

答案 2 :(得分:0)

问题是,函数返回factorial(n -1) * n这是一个表达式而没有单个递归调用。如果你设法在return语句中只有函数调用,那么该函数被称为 tail recursive ,并且一个好的编译器/解释器(我不确定Ruby是否能够使用它)可以做一些优化以避免使用堆栈。

这样的尾递归函数可能看起来像这样,但请注意我不是Ruby程序员,因此我既不习惯语法也不习惯解释器的功能。但是你可以自己尝试一下:

def factorial(n, result=1)
    n == 0 ? result : factorial(n-1, result * n)
end