4

Say I have a function like this:

user=> (def m {10 5, 5 2, 2 1})
#'user/m
user=> (defn hierarchy [x] (when x (cons x (hierarchy (get m x)))))
#'user/hierarchy
user=> (hierarchy 10)
(10 5 2 1)
user=> 

And obviously this is fine here because the stack depth will be small. But for this general type of problem, where I'm building a list that I want to return, the recursive call always ends up inside a cons call. How would I convert this to tail recursion, so that I can use recur and not take stack space?

0

4 Answers 4

3

Read up on accumulators.

In Clojure this specific problem could be solved by using lazy-seq. lazy-seq defers the computation, so stack overflows are (usually) not an issue.

(defn hierarchy
  [x]
  (when x
    (lazy-seq
      (cons x (hierarchy (get m x))))))
Sign up to request clarification or add additional context in comments.

2 Comments

Perhaps you could add that lazy-seq defers the computation of the recursive call until after the calling instance of the function exits, so the recursive calls actually never live on the same stack. "Holding onto the head" might still exhaust the memory but not because of a stack overflow.
You can still run into a stack overflow when you pile sequence on sequence on sequence. Example: (loop [x (seq some-input) s nil]) (if s (recur (next x) (concat (do-stuff (first x)) s)) s)). On realisation of the returned s you kick off a serious of realisations on the concat cascade, which can lead to a stack overflow.
3

You can solve this elegantly without using recursion:

(defn hierarchy [x]
  (take-while identity (iterate m x)))

2 Comments

You can even get rid of the partial and use m directly.
thanks, I was wondering if there was a way to do it without explicit recursion.
2

1st variant

(defn hierarchy* [res x]
  (if-not x
    res
    (recur (conj res x) (get m x))))

(defn hierarchy [x]
  (hierarchy* [] x))

2nd

(defn hierarchy [x]
  (loop [res []
         next-x x]
    (if-not next-x
      res
      (recur (conj res next-x) (get m next-x)))))

2 Comments

For 1st implementation you don't need to define separated function hierarchy*. In Clojure it's possible to define different behavior for different number of arguments clojure.org/functional_programming.
marking this as correct. I appreciate all answers, but this comes the closest to what I was trying to understand (even if there are other, possibly better, ways of doing this in clojure).
1

add lazy-seq:

(defn hierarchy [x] (when x (cons x (lazy-seq (hierarchy (get m x))))))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.