选项 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
- 只是递归调用的次数
为什么您的示例代码不是尾递归的?
解释 Wikipedia 文章的如何编写尾递归函数部分,我们可以重写这行代码:
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
尾递归的定义是在递归调用之后不能有额外的操作。正如我们在代码的重写版本中看到的那样,产生结果需要额外的操作。
资源