Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Common Lisp: Why does my tail-recursive function cause a stack overflow?

I have a problem in understanding the performance of a Common Lisp function (I am still a novice). I have two versions of this function, which simply computes the sum of all integers up to a given n.

Non-tail-recursive version:

(defun addup3 (n) 
  (if (= n 0)
    0   
    (+ n (addup (- n 1)))))

Tail-recursive version:

(defun addup2 (n) 
  (labels ((f (acc k)
              (if (= k 0)
                   acc 
                   (f (+ acc k) (- k 1)))))
  (f 0 n)))

I am trying to run these functions in CLISP with input n = 1000000. Here is the result

[2]> (addup3 1000000)
500000500000
[3]> (addup2 1000000)

*** - Program stack overflow. RESET

I can run both successfully in SBCL, but the non-tail-recursive one is faster (only by a little, but that seems strange to me). I've scoured Stackoverflow questions for answers but couldn't find something similar. Why do I get a stack overflow although the tail-recursive function is designed NOT to put all recursive function calls on the stack? Do I have to tell the interpreter/compiler to optimise tail calls? (I read something like (proclaim '(optimize (debug 1)) to set the debug level and optimize at the cost of tracing abilities, but I don't know what this does). Maybe the answer is obvious and the code is bullshit, but I just can't figure it out. Help is appreciated.

Edit: danlei pointed out the typo, it should be a call to addup3 in the first function, so it is recursive. If corrected, both versions overflow, but not his one

(defun addup (n) 
         "Adds up the first N integers"
         (do ((i 0 (+ i 1)) 
              (sum 0 (+ sum i)))
             ((> i n) sum)))

While it may be a more typical way to do it, I find it strange that tail recursion is not always optimised, considering my instructors like to tell me it's so much more efficient and stuff.

like image 538
oarfish Avatar asked May 18 '13 16:05

oarfish


People also ask

Can tail recursion cause stack overflow?

Tail recursion is a recursion of a function where it does not consumes stack space and hence prevents stack overflow. If the recursive function is made tail-recursive then it is more efficient than a non-tail-recursive function because every function call does not need to go on stack and pop when the call is done.

What causes stack overflow in recursion?

The most-common cause of stack overflow is excessively deep or infinite recursion, in which a function calls itself so many times that the space needed to store the variables and information associated with each call is more than can fit on the stack.

Does tail recursion use the stack?

As a rule, Scheme tail-recursive functions use constant stack space.

Does Common Lisp have tail recursion?

Some compilers for languages such as Common Lisp and C perform a limited form of "tail call optimization," but Scheme's treatment of tail calls, is more general, and standardized, so you can use recursion more freely in your programs, without fear of stack overflow if you code your routines tail-recursively.


2 Answers

There is no requirement for a Common Lisp implementation to have tail call optimization. Most do, however (I think that ABCL does not, due to limitations of the Java virtual machine).

The documentation of the implementation should tell you what optimization settings should be chosen to have TCO (if available).

It is more idiomatic for Common Lisp code to use one of the looping constructs:

(loop :for i :upto n
      :sum i)

(let ((sum 0))
  (dotimes (i n)
    (incf sum (1+ i))))

(do ((i 0 (1+ i))
     (sum 0 (+ sum i)))
    ((> i n) sum))

In this case, of course, it is better to use the "little Gauß":

(/ (* n (1+ n)) 2)
like image 155
Svante Avatar answered Sep 24 '22 02:09

Svante


Well, your addup3 just isn't recursive at all.

(defun addup3 (n) 
  (if (= n 0)
    0   
    (+ n (addup (- n 1))))) ; <--

It calls whatever addup is. Trying a corrected version in SBCL:

CL-USER> (defun addup3 (n) 
           (if (= n 0)
               0   
               (+ n (addup3 (- n 1)))))
ADDUP3
CL-USER> (addup3 100000)
Control stack guard page temporarily disabled: proceed with caution
;  ..
; Evaluation aborted on #<SB-SYS:MEMORY-FAULT-ERROR {C2F19B1}>.

As you'd expect.

like image 31
danlei Avatar answered Sep 22 '22 02:09

danlei