我一直在重写许多OCaml的标准库函数是尾递归最近。 鉴于这entailed直接的CPS转换,我留下令人费解,为什么默认版本不会这样写。
作为一个例子,在标准库,地图被定义为:
let rec map f = function
[] -> []
| a::l -> let r = f a in r :: map f l
我已经重写它是:
let map f l =
let rec aux l k = match l with
[] -> k []
| a::l -> aux l (fun rest -> k (f a :: rest))
in aux l (fun x -> x)
在我的经验中,非平凡的函数尾递归版本往往反向交易时间效率空间效率。 换句话说,在标准库中的函数可能很容易对短小的输入速度更快。
那么,你的代码是构建和沿封(每封盖的捕获前一个是一个“链接列表”传递k
)在堆中,而不是调用堆栈上帧的堆栈。
更常见的,相当于办法做到这一点尾递归是沿结果列表传递到目前为止(在倒车时,因为你只能有效地增加前),然后扭转它到底:
let map f l =
let rec aux l acc = match l with
[] -> List.rev acc
| a::l -> aux l (f a :: l)
in aux l
(这是基本相同List.rev (List.rev_map fl)
在这种情况下,积累的东西是结果列表到目前为止(反向),而不是关闭。 但效果是完全一样的。
在这两种情况下,我们需要对某种中间表示(比输入也不输出列表其它)的线性空间,所以在存储器使用的复杂性方面,有在所述非尾递归版本没有优势。 虽然这是事实,有在堆比栈更多的内存,所以使用尾递归版本可能会比非尾递归版本大名单的工作。 在绝对内存使用方面,积累了列表选项可能是最有效的,因为封锁和堆栈帧都有更多的开销。