Lambda 使用continuations处理F#中的树

Lambda 使用continuations处理F#中的树,lambda,f#,continuations,Lambda,F#,Continuations,我试图理解延续是如何工作的,我在Tomas Petricek和Jon Skeet的《真实世界函数式编程》一书中遇到了这个例子。但这真的让我头晕目眩,所以我必须寻求一些详细的帮助 type IntTree = | Leaf of int | Node of IntTree * IntTree let rec sumTreeCont tree cont = match tree with | Leaf(n) -> cont(n) | Node(left, rig

我试图理解延续是如何工作的,我在Tomas Petricek和Jon Skeet的《真实世界函数式编程》一书中遇到了这个例子。但这真的让我头晕目眩,所以我必须寻求一些详细的帮助

type IntTree = 
    | Leaf of int
    | Node of IntTree * IntTree

let rec sumTreeCont tree cont =
  match tree with
  | Leaf(n) -> cont(n)
  | Node(left, right) -> 
      sumTreeCont left (fun leftSum ->
        sumTreeCont right (fun rightSum ->
          cont(leftSum + rightSum)))

好吧,这是我自己能弄明白的。。。在第二个分支中,我们首先处理节点的左侧并传递lambda。这个lambda将实例化一个包含两个字段的闭包类,
right:IntTree
cont:(int->'a)
,这两个字段将由基格调用。但是,似乎“内部lambda”捕获了<代码>左撇子< /代码>,但我不太理解它是如何组合在一起的,我必须承认,当我试图弄清楚这一点时,我会感到有点眩晕。

如果你首先考虑这个表达式来计算树和:
let rec sumTree tree =
   match tree with
   | Leaf(n) -> n
   | Node(left, right) -> 
       sumTree left + sumTree right

此解决方案的问题是,由于堆栈帧分配过多,它会使大型树的堆栈溢出。解决方案是使用确保递归调用处于尾部位置,这意味着您不能在调用之后执行任何操作(在上述情况下,在递归调用之后执行加法)。在这种情况下,编译器可以消除不必要的堆栈帧,从而避免溢出。解决这个问题的技巧是使用连续传球方式,就像托马斯和乔恩的解决方案一样。如您所见,这里使用的continuations确保在递归调用之后不会执行任何操作。

我认为Christian的答案是一个很好的答案-continuation传递样式实际上只是对原始源代码所做的(并非如此)简单的机械转换。当您一步一步地执行此操作时,可能更容易看到:

1) 从原始代码开始(在这里,我将代码更改为每行只执行一个操作):

2) 添加continuation参数并调用它,而不是返回结果(这仍然不是尾部递归)。为了进行这种类型检查,我将continuation
funx->x
添加到两个子调用中,以便它们只返回结果的总和:

let rec sumTree tree cont =
   match tree with
   | Leaf(n) -> cont n
   | Node(left, right) -> 
       let leftSum = sumTree left (fun x -> x)
       let rightSum = sumTree right (fun x -> x)
       cont (leftSum + rightSum)
3) 现在,让我们将第一个递归调用更改为使用continuation传递样式-将主体的其余部分提升到continuation中:

let rec sumTree tree cont =
   match tree with
   | Leaf(n) -> cont n
   | Node(left, right) -> 
       sumTree left (fun leftSum ->
         let rightSum = sumTree right (fun x -> x)
         cont (leftSum + rightSum) )
4) 并对第二个递归调用重复相同的操作:

let rec sumTree tree cont =
   match tree with
   | Leaf(n) -> cont n
   | Node(left, right) -> 
       sumTree left (fun leftSum ->
         sumTree right (fun rightSum -> 
           cont (leftSum + rightSum) ))

在尝试理解这一点的过程中,我制作了一幅Visio绘图,我想我可以在这里分享它,以防它对其他人有所帮助。我意识到这可能会让一些人更加困惑,但对于视觉学习者(比如我)来说,我觉得通过画一个这样处理一棵树的例子,事情会变得更清楚


我强烈推荐阅读布赖恩·麦克纳马拉的关于变形(包括连续体)的博客系列,非常感谢您的澄清!到目前为止,我认为你的书写得很好,但延续的概念似乎需要一段时间我才能理解:)。答案很好。显然,步骤2)的转换需要应用于DU的所有情况:
|Leaf(n)->contn
@kaefer-thanking-fixed。
let rec sumTree tree cont =
   match tree with
   | Leaf(n) -> cont n
   | Node(left, right) -> 
       sumTree left (fun leftSum ->
         sumTree right (fun rightSum -> 
           cont (leftSum + rightSum) ))