选项 2:F# 尾调用
一种选择是重写您的方法,以便使用递归 尾调用。根据先前(维基百科)链接:
尾递归函数是递归的特殊情况,在该情况下,方法中执行的最后一条指令是递归调用。F# 和许多其他函数式语言可以优化尾递归函数;由于在递归调用之后不执行任何额外的工作,因此函数无需记住它来自哪里,因此无需在堆栈上分配额外的内存。
F# 通过告诉 CLR 在执行目标函数之前删除当前堆栈帧来优化尾递归函数。因此,尾递归函数可以无限递归而不消耗堆栈空间。
有一个警告 - Mono 不完全支持尾调用 - 您应该首先在 .Net 运行时中进行测试,然后再查看代码是否可以在 Mono 中运行。
这是您的示例函数,使用尾递归调用进行重写 - 它可以在 .NET(使用 Visual Studio 2010)和 Mono(Mono 版本 3.2.0,F# 3.0)中运行:
let f i =
let rec loop acc counter =
// display progress every 10k iterations
let j = counter % 10000
if j = 0 then
printf "Got up to %d\n" counter
stdout.Flush ()
if counter < 1000000000 then
// tail recursive
loop (acc + 1) (counter + 1)
else
acc
loop 0 i
对于输入值1:
let result = f 1
printf "result %d\n" result
输出:
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
对于输入值999,999,998:
let result = f 999999998
printf "result %d\n" result
输出:
Got up to 1000000000
result 2
上述代码使用两个变量来跟踪进度:
acc
- 累加器,用于存储计算结果
counter
- 仅是递归调用的次数
为什么你的示例代码不是尾递归?
重新表述维基百科文章中的
如何编写尾递归函数部分,我们可以重写
您代码的这一行:
if i = 1000000000 then 0 else 1 + f (i+1)
作为
if i = 1000000000 then 0
else
let intermediate = f (i+1) // recursion
let result = 1 + intermediate // additional operations
result
非常明确的定义了
尾递归,它指出在递归调用之后不能有其他操作。正如我们在重写代码的版本中所看到的那样,需要执行其他操作才能产生结果。
资源