I have been rewriting many OCaml standard library functions to be tail-recursive lately. Given that this has entailed straight-forward CPS transformation, I am left puzzling over why the default versions are not written this way.
As an example, in the standard library, map is defined as:
let rec map f = function
[] -> []
| a::l -> let r = f a in r :: map f l
I have rewritten it to be:
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)
In my experience, tail recursive versions of non-trivial functions often trade space efficiency against time efficiency. In other words, the functions in the standard library might easily be faster for smallish inputs.
Well, your code is building and passing along a "linked list" of closures (each of the closures captures the previous one as k
) in the heap instead of a stack of frames on the call stack.
A more common, equivalent way to do it tail-recursively is to pass along the result list so far (in reverse, since you can only efficiently add to the front), and then reverse it in the end:
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
(this is basically the same as List.rev (List.rev_map f l)
)
In this case, the thing that is accumulated is the list of results so far (in reverse), rather than a closure. But the effect is exactly the same.
In both cases we need linear space for some kind of intermediate representation (other than the input nor the output list), so in terms of complexity of memory usage, there is no advantage over the non-tail-recursive version. Although it is true that there is more memory on the heap than the stack, so using the tail-recursive version will probably work for bigger lists than the non-tail-recursive version. In terms of absolute memory usage, the accumulating the list option is probably the most efficient, because closures and stack frames both have more overhead.
来源:https://stackoverflow.com/questions/12012585/why-does-the-ocaml-std-lib-have-so-many-non-tail-recursive-functions