我一直在阅读F# core library sources(第2.0节),发现了一些相当有趣的东西:
List.foldBack
是通过可变数组实现的,与List.fold
不同,这非常简单。这是来源,您可能会发现它here:
let foldArraySubRight (f:OptimizedClosures.FSharpFunc<'T,_,_>) (arr: 'T[]) start fin acc =
let mutable state = acc
for i = fin downto start do
state <- f.Invoke(arr.[i], state)
state
// this version doesn't causes stack overflow - it uses a private stack
let foldBack<'T,'State> f (list:'T list) (acc:'State) =
// skipped optimized implementations for known lists
// It is faster to allocate and iterate an array than to create all those
// highly nested stacks. It also means we won't get stack overflows here.
let arr = toArray list
let arrn = arr.Length
foldArraySubRight f arr 0 (arrn - 1) acc
不使用延续的原因是什么?
像下面的代码一样天真似乎只比高度优化的库方法慢2-3倍。看来它真的“更快地分配和迭代数组”似乎值得怀疑。此外,它是尾递归,所以这里没有StackOverflow
我错过了什么吗?
let foldBack2 predicate acc list =
let rec loop list cont =
match list with
| [] -> cont acc
| h::t -> loop t (fun racc -> cont (predicate h racc))
loop list id
答案 0 :(得分:10)
我可以想到一些不使用CPS的原因:
列表不能轻易地向后遍历,因为无法顺序访问数组(向前或向后),向/从数组复制实际上非常有效。作为挑战,尝试为10,000 / 100,000 / 100,000,000元素编写更快的foldBack
。
答案 1 :(得分:5)
在实践中,您经常处理不会导致堆栈溢出的小列表。例如,OCaml中的F#List.foldBack
的对应部分List.fold_right不是尾递归或使用CPS。
作为用户,我们并不关心内部实施是什么。我们喜欢快速和尾递归List.foldBack
。例如,这个漂亮的split函数在F#中是尾递归的:
let split list = List.foldBack (fun x (l,r) -> x::r, l) list ([],[])