views:

193

answers:

2

I wrote 3 functions that count the number of times an-element appears in a-list. I tried various inputs and profiled it but I still dont know which function is the best in terms of stack usage efficiency and time efficiency. Please Help me out.

;; Using an accumulator
    (defn count-instances1 [a-list an-element]
      (letfn [(count-aux [list-aux acc]
                         (cond
                           (empty? list-aux) acc
                           :else (if (= (first list-aux) an-element)  
                                   (count-aux (rest list-aux) (inc acc))
                                   (count-aux (rest list-aux) acc))))]
        (count-aux a-list 0)))

;; Normal counting 
    (defn count-instances2 [a-list an-element]
     (cond
       (empty? a-list) 0
       :else
          (if (= (first a-list) an-element )
              (+ 1 (count-instances2 (rest a-list) an-element))
              (count-instances2 (rest a-list) an-element))))

;; using loop. does this help at all?
   (defn count-instances3 [a-list an-element]
        (loop [mylist a-list acount 0]
         (if (empty? mylist)
             acount
             (if (= (first mylist) an-element)
          (recur (rest mylist)(inc acount))
          (recur (rest mylist) acount)))))
A: 

Writing your code so the compiler/interperter can optmize it for tail recursion, should result in some performance increase and stack usage reduction. I think your normal counting function could qualifies for tail recursion so that one should be pretty fast. Not sure since I only dabble in Lisp as a hobby.

http://en.wikipedia.org/wiki/Tail_recursion

anio
Clojure cannot optimize tail calls due to limitations of the JVM.
Svante
Rich Hickey's comment on this was that it was better to have an explicit abstaction that works all the time (recur) than an implicit one that works almost all of the time (due to complexities of doing this on the JVM)
Arthur Ulfeldt
+3  A: 

The loop/recur version is the right way. Clojure cannot optimize tail calls due to limitations of the JVM.

Svante
That's not accurate. You should say that Clojure *chooses* not to optimize tail calls, because that's pretty difficult to achieve in a language (Java) that doesn't already have them. In fact there are some implementations of languages (eg, SISC -- http://sisc-scheme.org/) on top of the JVM that do optimize tail calls.
Eli Barzilay
Thats really strange. Why would it not want to optimize tail calls? It would have saved us a lot of hassles? Are there trade offs?
kunjaan
`recur` is nice because it's explicit and you can only use it from tail positions (the compiler complains otherwise) which catches instances where you think you're tail-recursing but you aren't. It could all be macro'd away automatically, but it's not that much hassle to replace your function name in the tail call with the symbol `recur`.
Brian Carper
My impression is that that "choice" is more or less forced on Clojure. I think that loop/recur is a nice construct, however real tail call optimization is a much more general concept than this.
Svante
SISC Scheme is interesting, maybe they have found some trick. I am sure that I read that Rich Hickey said it is difficult or impossible to optimize tail calls on the JVM.
Svante
"Difficult" is the right word. "Impossible" is just wrong. What SISC is doing is not new (and SISC itself is older than Clojure, IIRC).
Eli Barzilay
http://clojure-log.n01se.net/date/2008-05-19.html ...
kunjaan
Good find, kunjaan, that explains it.
Svante
Here's a relevant discussion of Tail Calls in JVM here @ SO : http://stackoverflow.com/questions/105834/does-the-jvm-prevent-tail-call-optimizations .
kunjaan