Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy sequence or recur for mathematical power function?

Tags:

clojure

As an exercise I implemented the mathematical power function. Once using recur:

(defn power [a n]
  (let [multiply (fn [x factor i]
                   (if (zero? i)
                     x
                     (recur (* x factor) factor (dec i))))]
  (multiply a a (dec n))))

user=> (time (dotimes [_ 10000] (power 2 512)))
"Elapsed time: 1839.406746 msecs"

And once with lazy-seq:

(defn power [a n]
  (letfn [(multiply [a factor]
            (lazy-seq
              (cons a (multiply (* a factor) factor))))]
  (nth (multiply a a) (dec n))))

user=> (time (dotimes [_ 10000] (power 2 512)))
"Elapsed time: 2162.297827 msecs"

Which implementation do you think is superior? I truly have no idea.. (I'd use recur because it's easier to understand.)

I read that lazy-seq is fast because it uses internal caching. But I don't see any opportunities for caching in my sample. Am I overlooking something?

Update
I posted the timings with the samples. It seems that recur is slightly faster here.

Regular recursion doesn't do too bad either:

(defn power [a n]
   (if (== n 1)
      a
      (* a (power a (dec n)))))

user=> (time (dotimes [_ 10000] (power 2 512)))
"Elapsed time: 1877.34521 msecs"
like image 836
StackedCrooked Avatar asked Dec 22 '22 03:12

StackedCrooked


1 Answers

First of all, the usual advice is to pick the correct algorithm first, worry about implementation details later (if your code is actually that sensitive to performance or might be used in contexts which are).

Then there are aesthetic considerations. The recur seems cleaner to me, just because it's the perfectly natural way to go about the problem. Using sequences makes sense when they enter the semantic picture somehow or, failing that, make the code significantly easier to write / understand / make performant. Nothing of the sort is the case here.

Finally, I'd definitely expect recur to be faster overall, if only because it avoids the unnecessary allocation & GC. Initial timings support this. There is indeed no opportunity to benefit from any sort of caching here, because you generate your sequence from scratch whenever power is called and never hold on to it after returning.

like image 168
Michał Marczyk Avatar answered Jan 13 '23 09:01

Michał Marczyk