Answers:
不,Ruby不执行TCO。但是,它也不执行TCO。
Ruby语言规范未提及TCO。它并没有说您必须这样做,但也没有说您不能这样做。您就是不能依靠它。
这与计划,其中语言规范要求的是所有的实现必须进行TCO。但是它也与Python不同,在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可以做到这一点。鲁比尼乌斯也可以做到。
但是JRuby和XRuby不支持TCO,而且可能不支持TCO,除非JVM本身获得了对TCO的支持。问题是这样的:如果您希望有一个快速的实现以及与Java的快速无缝集成,那么您应该与Java堆栈兼容,并尽可能使用JVM的堆栈。您可以使用蹦床或显式的延续传递样式轻松实现TCO,但是您不再使用JVM堆栈,这意味着每次您要调用Java或从Java调用Ruby时,都必须执行某种转换,这很慢。因此,XRuby和JRuby选择在TCO和延续性上进行速度和Java集成(基本上有相同的问题)。
这适用于所有想要与某些本身不支持TCO的主机平台紧密集成的Ruby实现。例如,我猜MacRuby将有同样的问题。
更新:这是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。它还解释了随之而来的一些问题:尾部调用优化:默认启用?
链接的简短摘录:
通常,尾递归优化包括另一种优化技术-“调用”到“跳转”翻译。我认为,很难应用这种优化,因为在Ruby的世界中很难识别“递归”。
下一个例子。“ else”子句中的fact()方法调用不是“尾调用”。
def fact(n)
if n < 2
1
else
n * fact(n-1)
end
end
如果要对事实()方法使用尾部调用优化,则需要按以下方式更改事实()方法(连续传递样式)。
def fact(n, r)
if n < 2
r
else
fact(n-1, n*r)
end
end
它可以具有但不能保证:
还可以通过在编译之前在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
这建立在约尔格和欧内斯特的答案上。基本上,它取决于实现。
我无法获得欧内斯特(Ernest)对MRI进行研究的答案,但这是可行的。我发现此示例适用于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