Is it possible to implement a tail recursive version of the quick sort algorithm (via the continuation pattern)? And if it is, how would one implement it?
Normal (not optimized) version:
let rec quicksort list =
match list with
| [] -> []
| element::[] -> [element]
| pivot::rest -> let ``elements smaller than pivot``, ``elements larger or equal to pivot``=
rest |> List.partition(fun element -> element < pivot)
quicksort ``elements smaller than pivot`` @ [pivot] @ quicksort ``elements larger or equal to pivot``
Direct style:
My first, naive translation is very similar to Laurent’s version, except indented a bit weirdly to make apparent that calls with continuations are really a kind of binding:
Contrarily to Laurent, I find it easy to check that
contis not forgotten: CPS functions translated from direct style have the property that the continuation is used linearily, once and only once in each branch, in tail position. It is easy to check that no such call was forgotten.But in fact, for most runs of quicksort (supposing you get a roughly logarithmic behavior because you’re not unlucky or you shuffled the input first), the call stack is not an issue, as it only grows logarithmically. Much more worrying are the frequent calls to
@wich is linear in its left parameter. A common optimization technique is to define functions not as returning a list but as “adding input to an accumulator list”:Of course this can be turned into CPS again:
Now a last trick is to “defunctionalize” the continuations by turning them into data structure (supposing the allocation of data structures is slightly more efficient than the allocation of a closure):
Finally, I chose the
function .. funstyle foreval_contto make it apparent that those were just pieces of code from the CPS version, but the following version is probably better optimized by arity-raising: