到目前为止,Go编程语言是否优化尾调用?如果没有,它是否至少优化了函数的尾递归调用?
在制定另一个SO问题的答案时,我在Mathematica中遇到了一些关于尾递归的奇怪行为.
在数学文档暗示,尾调用优化的可能被执行.但我自己的实验给出了相互矛盾的结果.对比,例如,以下两个表达式.第一个崩溃7.0.1内核,可能是由于堆栈耗尽:
(* warning: crashes the kernel! *)
Module[{f, n = 0},
f[x_] := (n += 1; f[x + 1]);
TimeConstrained[Block[{$RecursionLimit = Infinity}, f[0]], 300, n]
]
Run Code Online (Sandbox Code Playgroud)
第二个运行完成,似乎利用尾调用优化来返回有意义的结果:
Module[{f, n = 0},
f[x_] := Null /; (n += 1; False);
f[x_] := f[x + 1];
TimeConstrained[Block[{$IterationLimit = Infinity}, f[0]], 300, n]
]
Run Code Online (Sandbox Code Playgroud)
两个表达式都定义了尾递归函数f.在第一个函数的情况下,Mathematica显然认为复合语句的存在足以击败尾调用优化的任何机会.还要注意,第一个表达式由$RecursionLimit第二个表达式控制,第二个表达式由$IterationLimitMathematica以不同方式处理这两个表达式.(注意:上面提到的SO答案有一个较少设法的功能,成功利用尾部调用优化).
所以,问题是:有没有人知道Mathematica对递归函数进行尾调用优化的情况?在Mathematica文档或其他WRI材料中提及最终陈述将是理想的.投机也很受欢迎.
recursion wolfram-mathematica tail-recursion tail-call-optimization
可能重复:
为什么.net/C#不会消除尾递归?
请使用以下C#代码:
using System;
namespace TailTest
{
class MainClass
{
public static void Main (string[] args)
{
Counter(0);
}
static void Counter(int i)
{
Console.WriteLine(i);
if (i < int.MaxValue) Counter(++i);
}
}
}
Run Code Online (Sandbox Code Playgroud)
C#编译器(无论如何)我会将Counter方法编译成以下CIL:
.method private static hidebysig default void Counter (int32 i) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call void class [mscorlib]System.Console::WriteLine(int32)
IL_0006: ldarg.0
IL_0007: ldc.i4 2147483647
IL_000c: bge IL_0019
IL_0011: ldarg.0
IL_0012: ldc.i4.1
IL_0013: add
IL_0014: call void class TailTest.MainClass::Counter(int32)
IL_0019: ret
}
Run Code Online (Sandbox Code Playgroud)
上面代码的问题是它会导致堆栈溢出(在我的硬件上约为i = …
我喜欢的JavaScript到目前为止,并决定使用Node.js的为我的发动机的部分原因是因为这个,它声称的Node.js提供TCO.但是,当我尝试使用Node.js运行此代码(显然是尾部调用)时,会导致堆栈溢出:
function foo(x) {
if (x == 1) {
return 1;
}
else {
return foo(x-1);
}
}
foo(100000);
Run Code Online (Sandbox Code Playgroud)
现在,我做了一些挖掘,我找到了这个.在这里,似乎我应该这样写:
function* foo(x) {
if (x == 1) {
return 1;
}
else {
yield foo(x-1);
}
}
foo(100000);
Run Code Online (Sandbox Code Playgroud)
但是,这给了我语法错误.我试过它的各种排列,但在所有的情况下,Node.js的似乎不满的东西.
基本上,我想知道以下内容:
yield东西在Node.js中如何运作?一位朋友在Clojure中给了我这段代码片段
(defn sum [coll acc] (if (empty? coll) acc (recur (rest coll) (+ (first coll) acc))))
(time (sum (range 1 9999999) 0))
Run Code Online (Sandbox Code Playgroud)
并问我如何对付类似的Scala实现.
我写的Scala代码看起来像这样:
def from(n: Int): Stream[Int] = Stream.cons(n, from(n+1))
val ints = from(1).take(9999998)
def add(a: Stream[Int], b: Long): Long = {
if (a.isEmpty) b else add(a.tail, b + a.head)
}
val t1 = System.currentTimeMillis()
println(add(ints, 0))
val t2 = System.currentTimeMillis()
println((t2 - t1).asInstanceOf[Float] + " msecs")
Run Code Online (Sandbox Code Playgroud)
底线是:Clojure中的代码在我的机器上运行大约1.8秒并且使用少于5MB的堆,Scala中的代码运行大约12秒并且512MB的堆是不够的(如果我设置了它,它完成计算堆到1GB).
所以我想知道为什么在这种特殊情况下Clojure会更快更轻薄?你有一个Scala实现在速度和内存使用方面有类似的行为吗?
请不要发表宗教言论,我的兴趣在于找出主要是什么使得clojure在这种情况下如此快速,并且如果在scala中更快地实现算法.谢谢.
performance scala tail-recursion clojure tail-call-optimization
如何在Java中实现无堆栈递归?
似乎出现最多的词是"蹦床",我不知道这意味着什么.
有人在IN DETAIL中解释如何在Java中实现无堆栈递归吗?还有什么是"蹦床"?
如果你不能提供其中任何一个,你能指出我正确的方向(即一本书来阅读它或一些教导所有这些概念的教程)?
显然,对于Python是否需要尾调用优化,存在很大的争议.当有人向Guido发送SICP副本时,这个问题就出现了,因为他没有"得到它".我和Guido在同一条船上.我理解尾调用优化的概念.我真的想不出Python真正需要它的任何理由.
为了让我更容易理解,有人可以给我一些代码片段,使用TCO可以大大简化吗?
TL;博士;
在C#中,你是否保证一个懒惰的迭代器函数只调用它本身并且有一个有效的递归退出条件不会导致堆栈溢出?
详细问题:
我知道通常你不能保证由C#编译器(或JIT)生成的尾调用优化(TCO)指令,所以虽然你可能得到TCO,但是没有保证.
鉴于TCO的这种认识,我想知道懒惰的迭代器函数(使用yield return等)是否因为它们作为协程的性质 - 每个尾部调用一个甚至占用堆栈空间?由于它们的重新进入,我对协同程序的直觉是默认情况下每个尾调用都被优化为跳出函数并从父框架跳到下一个函数而不是创建新框架的能力似乎很自然.
这是C#中的行为,还是C#迭代器函数的递归调用是从当前创建一个新帧而不是弹出到父帧并使用新参数重新输入?
例:
public static IEnumerable<IEnumerable<T>> GeneratePermutations<T>(this IEnumerable<T> choices, int numberToChoose)
{
if (numberToChoose == 1)
{
foreach (var choice in choices)
yield return new T[] { choice };
yield break;
}
var subPermutations = choices.SelectMany(choice =>
choices.Where(elem => !EqualityComparer<T>.Default.Equals(elem, choice))
.GeneratePermutations(numberToChoose - 1)
.Select(permutation => (new T[] { choice }).Concat(permutation)));
foreach (var perm in subPermutations)
yield return perm;
}
Run Code Online (Sandbox Code Playgroud)
我的直觉基于上面的例子subPermutations只是一个堆积的计算,它似乎在调用迭代它,它可以知道它是一个堆积的计算(它是函数sig的一部分,它是一个迭代器函数),因此立即跳转超出它的当前帧并将堆积的计算扩展到一个新的帧 - 在尝试递归调用 …
JVM实现在哪里不同(许可除外)?每个JVM是否都为通用处理实现Type Erasure?
两者之间的区别在哪里:
.....使用Tail-Call-Optimization处理其中一个?
可能重复:
为什么JVM仍然不支持尾调用优化?
我在网上看到了很多不同的答案,所以我想我会问专家.
java ×3
recursion ×3
.net ×2
c# ×2
clojure ×1
generics ×1
go ×1
javascript ×1
jvm ×1
mono ×1
node.js ×1
performance ×1
python ×1
scala ×1
yield-return ×1