我一直在尝试理解延续/CPS,并且从我能收集到的内容中,它建立了一个延迟的计算,一旦我们到达列表的末尾,我们就会调用最终的计算。
我不明白的是,当 CPS 看起来类似于根据示例 1 中的幼稚方法构建嵌套函数时,为什么 CPS 会阻止 stackoverflow。对不起,很长的帖子,但试图展示这个想法(以及可能出错的地方)基本:
所以:
let list1 = [1;2;3]
示例 1:“天真的方法”
let rec sumList = function
|[] -> 0
|h::t -> h + sumList t
因此,当它运行时,它会迭代地导致:
1 + sumList [2;3]
1 + (2 + sumList [3])
1 + (2 + (3 + 0))
所以嵌套(和溢出问题)可以通过尾递归来克服——运行一个累加器,即
“示例 2:尾递归”
let sumListACC lst =
let rec loop l acc =
match l with
|[] -> acc
|h::t -> loop t (h + acc)
loop lst 0
IE,
sumList[2;3] (1+0)
sumList[3] (2+1)
sumList[] (3+3)
因此,因为累加器在每一步都被评估,所以没有嵌套,我们避免了爆栈。清除!
接下来是 CPS,我知道当我们已经有一个累加器但函数不是尾递归时,例如使用 Foldback,这是必需的。尽管在上面的示例中不需要,但将 CPS 应用于此问题会给出:
“示例 3:CPS”
let sumListCPS lst =
let rec loop l cont =
match l with
|[] -> cont 0
|h::t -> loop t (fun x -> cont( h + x))
loop lst (fun x -> x)
据我了解,迭代地这可以写成:
loop[2;3] (fun x -> cont (1+x))
loop[3] (fun x ->cont (1+x) -> cont(2+x))
loop[] (fun x -> cont (1+x) -> cont(2+x) -> cont (3+x)
然后从右边依次减少,最后一个x = 0
即:
cont(1+x)-> cont(2+x) -> cont (3+0)
cont(1+x)-> cont(2+x) -> 3
cont(1+x) -> cont (2+3)
- ...
cont (1+5) -> 6
我想这类似于:
cont(1+cont(2+cont(3+0)))
(1+(2+(3+0)))
对原始帖子的更正 - 意识到它是从右侧评估的,例如替换为上述示例cont(h +x)
的cont(h+2*x)
产量17
,与:(1+2*(2+2*(3+2*0)))
即,正是我们在示例 1 中开始的地方,基于此,因为我们仍然需要跟踪我们来自哪里,为什么使用它可以防止示例 1 遭受的溢出问题?
我知道它没有,我哪里出错了?
我已经阅读了以下帖子(多次),但上述困惑仍然存在。
http://www.markhneedham.com/blog/2009/06/22/f-continuation-passing-style/
http://codebetter.com/matthewpodwysocki/2008/08/13/recursing-on-recursion-continuation-passing/
http://lorgonblog.wordpress.com/2008/04/05/catamorphisms-part-one/