使用continuation将二进制递归转换为尾递归

时间:2012-03-02 09:51:04

标签: f# functional-programming ocaml tail-recursion continuations

当我正在阅读编程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,我们也无法将二进制递归完全转换为尾递归?

2 个答案:

答案 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