6

我知道这与人们在询问堆栈溢出问题时遇到的问题有些相反,但是如果我创建一个函数并按如下方式调用它,我永远不会收到任何错误,并且应用程序只会磨碎我的核心CPU,直到我强制退出它:

let rec recursionTest x =
    recursionTest x

recursionTest 1

当然我可以改变它,所以它实际上是这样的:

let rec recursionTest (x: uint64) =
    recursionTest (x + 1UL)

recursionTest 0UL

这样我可以偶尔在我的代码中放置一个断点,然后看到 x 的值上升得很快,但它仍然没有抱怨。F# 不介意无限递归吗?

4

3 回答 3

9

您的recursionTest函数是尾递归的,这意味着所有递归调用都发生在“尾位置”,即作为函数中的最后一个动作。这意味着 F# 编译器不需要为递归调用分配新的堆栈帧,因此不会发生堆栈溢出。

尾递归是尾调用的一种特殊情况,调用是对自身而不是其他函数。

于 2012-11-30T23:05:56.087 回答
4

通常,F# 发出 .NET 尊重的尾调用指令:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.tailcall(v=vs.95).aspx

在某些特定的简单情况下,例如您的情况,F# 将使用递归的程序重写为使用循环的等效程序。

于 2012-11-30T23:06:54.700 回答
3

这是尾调用优化的一个示例,因此编译器将其优化为一个简单的循环。看到这个:https ://devblogs.microsoft.com/fsharpteam/tail-calls-in-f/

尝试这样的事情:

let rec recursionTest x =
    recursionTest x + recursionTest (x * 2)
于 2012-11-30T23:05:24.113 回答