尾递归

可以使用迭代来表达许多递归算法。例如,最大的公分母函数可以递归写入

def gdc (x, y)
  return x if y == 0
  return gdc(y, x%y)
end

或迭代地:

def gdc_iter (x, y)
  while y != 0 do
    x, y = y, x%y
  end

  return x
end

这两种算法在理论上是等价的,但递归版本存在 SystemStackError 的风险。但是,由于递归方法以对自身的调用结束,因此可以对其进行优化以避免堆栈溢出。另一种说法: 如果编译器知道在方法结束时查找递归方法调用,则递归算法可以产生与迭代相同的机器代码。Ruby 默认情况下不进行尾调用优化,但你可以使用以下命令将其打开

RubyVM::InstructionSequence.compile_option = {
  tailcall_optimization: true,
  trace_instruction: false
}

除了打开尾调用优化之外,你还需要关闭指令跟踪。不幸的是,这些选项仅适用于编译时,因此你需要从另一个文件中提取递归方法或者方法定义:

RubyVM::InstructionSequence.new(<<-EOF).eval
  def me_myself_and_i
    me_myself_and_i
  end
EOF
me_myself_and_i # Infinite loop, not stack overflow

最后,最后的返回调用必须返回方法,返回方法。这意味着你需要重新编写标准阶乘函数:

def fact(x)
  return 1 if x <= 1
  return x*fact(x-1)
end

对于这样的事情:

 def fact(x, acc=1)
   return acc if x <= 1
   return fact(x-1, x*acc)
 end

此版本通过默认为 1 的第二个(可选)参数传递累计和。

进一步阅读: RubyTailin’Ruby 中的 尾调优化