当我正在阅读编程F#书时,我在第195页找到了示例代码段,如下所示:
type ContinuationStep<'a> =
| Finished
| Step of 'a * (unit -> ContinuationStep<'a>)
let iter f binTree =
let rec linearize binTree cont =
match binTree with
| Empty -> cont()
| Node(x, l, r) ->
Step(x, (fun () -> linearize l (fun() -> linearize r cont)))
let steps = linearize binTree (fun () -> Finished)
let rec processSteps step =
match step with
| Finished -> ()
| Step(x, getNext)
-> f x
processSteps (getNext())
processSteps steps
通过使用continuation,遍历二进制文件的二进制递归已转换为尾递归函数processSteps
。我的问题是另一个函数linearize
似乎是非尾递归的。这是否意味着即使使用continuation,我们也无法将二进制递归完全转换为尾递归?
答案 0 :(得分:7)
linearize
是尾递归的:你不需要从递归调用中返回来继续计算。
fun () -> linearize l (fun() -> linearize r cont)
不会致电linearize
。计算暂停,直到processSteps
调用getNext ()
。
答案 1 :(得分:6)
这个例子有点微妙,因为它不使用普通的continuation,而是构建一个可以逐步评估的结构。在通常进行递归调用的地方,它返回一个值Step
,其中包含您(递归)调用的函数。
在第二种情况下,linearize
函数返回一个Step
,其中包含一个递归调用linearize
的函数,但它不会立即进行递归呼叫。所以函数不进行递归调用(它只存储一个递归引用)。
当你查看processSteps
时,考虑程序是否是尾递归是有意义的,因为它实际上是循环 - 这是尾递归的,因为它运行Step
Step
没有为每个Step
保留堆栈空间。
如果你想构建一个列表而不是一堆懒惰的步骤,那么你必须立即在延续中对linearize
进行递归调用:
let rec linearize binTree cont =
match binTree with
| Empty -> cont []
| Node(x, l, r) ->
linearize l (fun l -> linearize r (fun v -> cont (x::v)))
这与上一个函数基本相同,但它实际上调用linearize
而不是构建包含将调用Step
的函数的linearize
。