功能语言导致使用递归来解决许多问题,因此其中许多都执行尾调用优化(TCO)。 TCO导致从另一个函数调用函数(或者本身,在这种情况下,这个特性也称为Tail Recursion Elimination,它是TCO的一个子集),作为该函数的最后一步,不需要新的堆栈帧,这减少了开销和内存使用。
Ruby显然已经从函数式语言(lambdas,map等等函数)中“借用”了许多概念,这让我很好奇:Ruby是否执行尾调优化?
答案 0 :(得分:123)
不,Ruby不执行TCO。但是,它也不会不执行TCO。
Ruby语言规范没有提及TCO的任何内容。它并没有说你必须这样做,但它也没有说你不能这样做。你不能依赖就可以了。
这与Scheme不同,其中语言规范要求 所有实现必须执行TCO。但它也与Python不同,Guido van Rossum在多次(几天前的最后一次)非常清楚Python实现不应该执行TCO。
Yukihiro Matsumoto对TCO表示同情,他只是不想强制所有实现来支持它。不幸的是,这意味着您不能依赖TCO,或者如果您这样做,您的代码将不再可移植到其他Ruby实现。
因此,一些Ruby实现执行TCO,但大多数不执行。例如,YARV支持TCO,尽管(目前)你必须在源代码中明确取消注释一行并重新编译VM,以激活TCO - 在未来版本中,默认情况下,它将在实现证明之后启用稳定。 Parrot虚拟机本身支持TCO,因此Cardinal也可以非常轻松地支持它。 CLR对TCO有一些支持,这意味着IronRuby和Ruby.NET可能会这样做。 Rubinius也可能会这样做。
但JRuby和XRuby不支持TCO,他们可能不会,除非JVM本身获得对TCO的支持。问题在于:如果您希望快速实现,并与Java快速无缝集成,那么您应该与Java堆栈兼容并尽可能使用JVM的堆栈。您可以使用trampolines或显式的continuation-passing方式轻松实现TCO,但是您不再使用JVM堆栈,这意味着每次要调用Java或从Java调用Ruby时,都必须执行某种类型的转换,这很慢。因此,XRuby和JRuby选择了速度和Java集成而不是TCO和延续(基本上存在同样的问题)。
这适用于所有希望与某些本地不支持TCO的主机平台紧密集成的Ruby实现。例如,我猜MacRuby会遇到同样的问题。
答案 1 :(得分:42)
更新:以下是Ruby中TCO的精彩解释:http://nithinbekal.com/posts/ruby-tco/
更新:您可能还需要查看 tco_method gem:http://blog.tdg5.com/introducing-the-tco_method-gem/
在Ruby MRI(1.9,2.0和2.1)中,您可以通过以下方式打开TCO:
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
有人建议在Ruby 2.0中默认启用TCO。它还解释了随之而来的一些问题:Tail call optimization: enable by default?.
链接的简短摘录:
通常,尾递归优化包括另一种优化技术 - “调用”到“跳转”转换。在我看来, 因为识别,很难应用这种优化 在Ruby的世界里,“递归”很难。
下一个例子。 “else”子句中的fact()方法调用不是“尾巴” 呼叫”。
def fact(n)
if n < 2
1
else
n * fact(n-1)
end
end
如果要在fact()方法上使用尾调用优化,则需要 更改fact()方法如下(继续传递样式)。
def fact(n, r)
if n < 2
r
else
fact(n-1, n*r)
end
end
答案 2 :(得分:12)
它可以但不能保证:
答案 3 :(得分:4)
在编译之前,还可以通过在vm_opts.h中调整几个变量来编译TCO: https://github.com/ruby/ruby/blob/trunk/vm_opts.h#L21
// vm_opts.h
#define OPT_TRACE_INSTRUCTION 0 // default 1
#define OPT_TAILCALL_OPTIMIZATION 1 // default 0
答案 4 :(得分:2)
这建立在Jörg和Ernest的答案之上。 基本上它取决于实施。
我无法得到欧内斯特对MRI工作的答案,但这是可行的。 我发现this example适用于MRI 1.9到2.1。这应该打印一个非常大的数字。如果未将TCO选项设置为true,则应该出现“堆栈太深”错误。
source = <<-SOURCE
def fact n, acc = 1
if n.zero?
acc
else
fact n - 1, acc * n
end
end
fact 10000
SOURCE
i_seq = RubyVM::InstructionSequence.new source, nil, nil, nil,
tailcall_optimization: true, trace_instruction: false
#puts i_seq.disasm
begin
value = i_seq.eval
p value
rescue SystemStackError => e
p e
end