Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Clojure, is it possible to combine memoization and tail call optimization?

In clojure, I would like to write a tail-recursive function that memoizes its intermediate results for subsequent calls.

[EDIT: this question has been rewritten using gcd as an example instead of factorial.]

The memoized gcd (greatest common divisor) could be implemented like this:

(def gcd (memoize (fn [a b] 
   (if (zero? b) 
       a 
       (recur b (mod a b)))) 

In this implementation, intermediate results are not memoized for subsequent calls. For example, in order to calculate gcd(9,6), gcd(6,3) is called as an intermediate result. However, gcd(6,3) is not stored in the cache of the memoized function because the recursion point of recur is the anonymous function that is not memoized.

Therefore, if after having called gcd(9,6), we call gcd(6,3) we won't benefit from the memoization.

The only solution I can think about will be to use mundane recursion (explicitely call gcd instead of recur) but then we will not benefit from Tail Call Optimization.

Bottom Line

Is there a way to achieve both:

  1. Tail call optimization
  2. Memoization of intermediate results for subsequent calls

Remarks

  1. This question is similar to Combine memoization and tail-recursion. But all the answers there are related to F#. Here, I am looking for an answer in clojure.
  2. This question has been left as an exercise for the reader by The Joy of Clojure (chap 12.4). You can consult the relevant page of the book at http://bit.ly/HkQrio.
like image 717
viebel Avatar asked Mar 27 '12 21:03

viebel


1 Answers

in your case it's hard to show memoize do anything with factorial because the intermediate calls are unique, so I'll rewrite a somewhat contrived example assuming the point is to explore ways to avoid blowing the stack:

(defn stack-popper [n i] 
    (if (< i n) (* i (stack-popper n (inc i))) 1)) 

which can then get something out of a memoize:

(def stack-popper 
   (memoize (fn [n i] (if (< i n) (* i (stack-popper n (inc i))) 1))))

the general approaches to not blowing the stack are:

use tail calls

(def stack-popper 
    (memoize (fn [n acc] (if (> n 1) (recur (dec n) (* acc (dec n))) acc))))

use trampolines

(def stack-popper 
    (memoize (fn [n acc] 
        (if (> n 1) #(stack-popper (dec n) (* acc (dec n))) acc))))
(trampoline (stack-popper 4 1))

use a lazy sequence

(reduce * (range 1 4))

None of these work all the time, though I have yet to hit a case where none of them work. I almost always go for the lazy ones first because I find them to be most clojure like, then I head for tail calling with recur or tramplines

like image 124
Arthur Ulfeldt Avatar answered Sep 22 '22 04:09

Arthur Ulfeldt