--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
(defn quicksort [l]
(letfn [(qsort [[pivot & xs]]
(when pivot
(let [smaller #(< % pivot)]
(lazy-cat (qsort (filter smaller xs))
[pivot]
(qsort (remove smaller xs))))))]
(qsort (shuffle l))))
the Scheme version of quicksort is not tail-recursive since append is
called on the return value of the recursive calls. Thus, also in
Scheme this eats up your stack. That your Scheme code can sort larger
sequences simple means that your Scheme implementation has a larger
stack than the JVM with standard settings.
Basically, the only difference between Scheme and Clojure with respect
to tail-recursion is that Scheme automatically optimizes the recursive
call when it appears in a tail-call position whereas in Clojure you
have to explicitly call recur (or trampoline for mutual recursion) if
you want tail-call optimization.
Since quicksort requires two recursive calls which then have to be
combined it is not completely trivial to implement it in a tail-
recursive, i.e. iterative, fashion. There is a general method which
can be applied, namely continuation passing style (CPS), but it might
look a little odd if you haven't seen it before. Basically, you use a
variable holding a chain of closures which resemble the stack. I found
a rather nice exposition in this blog post:
http://www.enrico-franchi.org/2011/09/clojure-quicksort-in-continuation.html
Cheers,
Nils
Well yes, but they are not explicitly specified since Scheme
automatically optimizes tail calls. Consider this example:
(define (fact n)
(if (< n 2)
1
(* n (fact (- n 1)))))
This is not tail recursive and eventually blows the stack ... in
Scheme and in Clojure.
(define (fact-accu n res)
(if (< n 2)
res
(fact-accu (- n 1) (* n res))))
Here, the recursive call is in tail position and gets optimized by the
Scheme compiler. In Clojure you would have to call recur instead,
otherwise your stack grows. In Scheme this is not necessary since the
optimization is always done if possible. Thus, there is no special
syntactic marker, i.e. reserved word, for this. (Same holds for mutual
recursion <-> trampoline).
In Common Lisp the situation is somewhat unfortunate, since tail call
optimization is implementation dependent. So, you cannot rely on it
and therefore loop/tagbody etc are your friends there.
Hope this helps,
Nils
>
> John Holland
Trampoline and recur are a poor man's tail-call-optimization; and, if
by "the problem," you mean that a call-stack grows linearly with its
recursive depth: yeah, even Scheme is susceptible to stack-growth if
your calls aren't "properly tail-recursive."
See R5RS s. 3.5 [1], "Proper tail recursion":
A tail call is a procedure call that occurs in a tail context;
e.g. the last expression within the body of a lambda expression.
William Clinger wrote a paper that formalizes proper tail recursion in
more depth [2].
Suffice to say, the na�ve recursive implementation of quick sort
contains at least one non-tail-call; and, just for kicks, here's an
implementation of quicksort in Joy (a so-called concatenative
language):
[small] [] [uncons [>] split] [swapd cons concat] binrec
Joy, too, implements recursion (through `binrec') without growing the
call-stack.
Footnotes:
[1] http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-6.html#%_sec_3.5
[2] ftp://ftp.ccs.neu.edu/pub/people/will/tail.pdf