【发布时间】:2010-09-10 00:10:48
【问题描述】:
我有一个深度递归函数,理论上即使输入很大,它也应该能很好地工作。问题是在撰写本文时我忘记了 C# 并没有很好地进行尾调用优化(如果有的话),所以对于任何足够复杂的输入,我都会得到 StackOverflowExceptions。该方法的基本结构是两个大方法,每个方法调用另一个。
public object Simplify(object param) {
if (IsSimple(param))
return param;
else
return Expand(param);
}
private object Expand(object param) {
return Simplify(ProcessExpansion(param));
}
IsSimple 和 ProcessExpansion 都有一个相对固定的堆栈深度——唯一的问题是 Simplify 和 Expand 之间的递归。您将如何减少此处的堆栈深度?
我正在考虑返回代表来计算结果,但这似乎有点矫枉过正 - 必须有一个更简单的方法。这是我对解决方案的想法(它不是很完善,因为我一直认为我一定是在这里做错了什么):
public object Simplify(object param) {
object result = () => SimplifyInternal(param);
while (result is Func<object>)
result = ((Func<object>)result)();
return result;
}
private object SimplifyInternal(object param) {
if (IsSimple(param))
return param;
else
return Expand(param);
}
private object Expand(object param) {
return () => SimplifyInternal(ProcessExpansion(param));
}
所以我的两个问题是:
- 这个解决方案有什么可怕的错误?
- 你能想到更好的吗?
【问题讨论】:
-
请注意,.NET 4.0 x64 运行时优化了尾调用,而 x86 则没有。完全愚蠢,我知道。
-
为什么不在 F# 中编程?我宁愿让编译器优化它(语言功能),然后祈祷抖动会解决它。
-
尽管我很喜欢 F#,但如果可以的话,我会将此评论排名。建议您将整个项目切换到 F# 只是因为您需要在某一时刻进行尾调用优化,这不是很有建设性。
-
@Brian True,您可以只将部分代码移植到 F#。尽管如此,我发现当你忙着尝试自己解决问题时,有人插入另一种语言有点笨拙——尤其是在这样做完全有可能和实际的情况下(有一个正式的证明您可以将所有递归转换为循环的地方,反之亦然)。
-
好吧,OP 的问题最初是关于尾调用优化(尽管在 C# 中),但我不明白我们在这里使用的这种多语言/多范式框架的问题。东西总是在扩展/发展,知道你的选择永远不会对你产生负面影响......
标签: c# tail-recursion tail-call-optimization