Direct style: let rec quicksort list = match list with | -> | element -> element | pivot::rest -> let left, right = List. Partition (fun element -> element cont | element:: -> cont element | pivot::rest -> let left, right = List. Partition (fun element -> element quicksort right (fun sorted_right -> cont (sorted_left @ pivot @ sorted_right))) let qsort li = quicksort li (fun x -> x) Contrarily to Laurent, I find it easy to check that cont is 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": let rec quicksort list accu = match list with | -> accu | element:: -> element::accu | pivot::rest -> let left, right = List. Partition (fun element -> element cont accu | element:: -> cont (element::accu) | pivot::rest -> let left, right = List.
Partition (fun element -> element quicksort left (pivot :: sorted_right) cont) let qsort li = quicksort li (fun x -> x) 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): type 'a cont = | Left of 'a list * 'a * 'a cont | Return let rec quicksort list accu cont = match list with | -> eval_cont cont accu | element:: -> eval_cont cont (element::accu) | pivot::rest -> let left, right = List. Partition (fun element -> element (fun sorted_right -> quicksort left (pivot :: sorted_right) cont) | Return -> (fun x -> x) let qsort li = quicksort li Return Finally, I chose the function .. fun style for eval_cont to 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: and eval_cont cont accu = match cont with | Left (left, pivot, cont) -> quicksort left (pivot :: accu) cont | Return -> accu.
Direct style: let rec quicksort list = match list with | -> | element -> element | pivot::rest -> let left, right = List. Partition (fun element -> element cont | element:: -> cont element | pivot::rest -> let left, right = List. Partition (fun element -> element quicksort right (fun sorted_right -> cont (sorted_left @ pivot @ sorted_right))) let qsort li = quicksort li (fun x -> x) Contrarily to Laurent, I find it easy to check that cont is 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": let rec quicksort list accu = match list with | -> accu | element:: -> element::accu | pivot::rest -> let left, right = List.
Partition (fun element -> element cont accu | element:: -> cont (element::accu) | pivot::rest -> let left, right = List. Partition (fun element -> element quicksort left (pivot :: sorted_right) cont) let qsort li = quicksort li (fun x -> x) 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): type 'a cont = | Left of 'a list * 'a * 'a cont | Return let rec quicksort list accu cont = match list with | -> eval_cont cont accu | element:: -> eval_cont cont (element::accu) | pivot::rest -> let left, right = List. Partition (fun element -> element (fun sorted_right -> quicksort left (pivot :: sorted_right) cont) | Return -> (fun x -> x) let qsort li = quicksort li Return Finally, I chose the function .. fun style for eval_cont to 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: and eval_cont cont accu = match cont with | Left (left, pivot, cont) -> quicksort left (pivot :: accu) cont | Return -> accu.
– Laurent Apr 12 at 15:53 I have tried your last function, it's slightly faster than the "naive translation", but there's not a big difference. Both versions are still extremely slow sorting functions (as was the original one). Anyway, it's still a fun exercise.
– Laurent Apr 12 at 16:32 1 @Laurent I would be more interested in a comparison of the direct-style accumulator-using function wrt. The simplest one. – gasche Apr 12 at 16:35 1 @Laurent, Reynolds defunctionalization is the last step in compiling a higher-order functional language to code that doesn't require the machinery necessary to implement closures.
This in fact allows to compile easily such languages as Scheme that have first-class continuations down to C or assembler. – Matías Giovannini Apr 120 at 12:45.
Quick attempt, seeems to work: let rec quicksort list cont = match list with | -> cont | element:: -> cont element | pivot::rest -> let ``elements smaller than pivot``, ``elements larger or equal to pivot`` = rest |> List. Partition (fun element -> element quicksort ``elements larger or equal to pivot`` (fun y -> cont (x @ pivot @ y))) > quicksort 2; 6; 3; 8; 5; 1; 9; 4 id;; val it : int list = 1; 2; 3; 4; 5; 6; 8; 9 Edit: Of course, this code is highly inefficient. I hope nobody will use it in real code.
The code was not difficult to write, but continuations might be difficult to read and can be error-prone (it's easy to forget a call to cont). If you want to play more, you can write a continuation monad (Brian wrote a blog post about it).
Continuation monad (stolen from here) can also be used (usually makes code more readable): type ContinuationMonad() = // ma -> (a -> mb) -> mb member this. Bind (m, f) = fun c -> m (fun a -> f a c) // a -> ma member this. Return x = fun k -> k x // ma -> ma member this.
ReturnFrom m = m let cont = ContinuationMonad() // Monadic definition of QuickSort // it's shame F# doesn't allow us to use generic monad code // (we need to use 'cont' monad here) // otherwise we could run the same code as Identity monad, for instance // producing direct (non-cont) behavior let rec qsm = function | -> cont. Return |x::xs -> cont { let l,r = List. Partition ((>=)x) xs let!
Ls = qsm l let! Rs = qsm r return (ls @ x :: rs) } // Here we run our cont with id let qs xs = qsm xs id printf "%A" (qs 2;6;3;8;5;1;9;4).
– Yet Another Geek Apr 15 at 17:51 It is ordinary F# "greater than or equals" arithmetic operator (has nothing to do with Haskell's (>>=) operator which is represented here by this. Bind method) – Ed'ka Apr 16 at 1:26 Ah yes, maybe I should have paid more attention to the sematics of the function call instead of the syntax – Yet Another Geek Apr 16 at 9:39.
I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.