尾递归
可以使用迭代来表达许多递归算法。例如,最大的公分母函数可以递归写入 :
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 的第二个(可选)参数传递累计和。
进一步阅读: Ruby 和 Tailin’Ruby 中的 尾调优化 。