[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: tail recursion



  Date: Tue, 27 Feb 90 11:25:29 CST
  From: Larry Smith <lsmith@cli.com>
  To: slug@warbucks.ai.sri.com
  Subject: tail recursion
  
  
  A function like the following keeps blowing the control stack. 
  
  (defun doit-list (list collector)
    (if (atom list) collector
        (doit-list (cdr list)
                   (cons (doit (car list))
                         collector))))
  
  Is there a way to tell the lispm compiler that it's OK to turn the
  tail recursion into iteration? [Let's assume I don't want to recode it
  into a LOOP or MAPCAR or anything else.]
  
  Ah. I have just found a mention of compiler:add-optimizer in Volume 4,
  page 135 of the genera 7.2 manual. I can do what I
  need with this, painfully. The following turns 
  (doit-list x nil) into a LOOP.
  
  (compiler:add-optimizer doit-list turn-doit-list-into-loop)
  (defun turn-doit-list-into-loop (form)
     ;; form is a call to doit-list.
     (let ((arg1 (cadr form))
           (arg2 (caddr form)))
       (if (null arg2) `(loop for xyzzy in ,arg1 collect (doit xyzzy))
           form)))
  
  I realize there are at least three bugs in that transformation. That's
  one reason why I'd rather not use this feature if there's a more
  reasonable way. Another is that I write a lot of functions similar to
  doit-list, and don't want to recode the optimizer every time.
  
Unfortunately, the current compiler does not do tail recursion.  Many
people wish it did, but i seems harder than it looks.

You could try using a macro like this to generate loops for you:

(defmacro deftail (name args &body body)
  `(defun ,name ,args
     (macrolet
       ((tail-call ((call-name &rest call-args))
	  (if (not (eq call-name ',name))
	      (error "can only tail-call ~a" ',name)
	  `(progn
	     ,@(map 'list #'(lambda (a v) `(setq ,a ,v)) ',args call-args)
	     (go :start)))))
       (prog ()
	 :start
	 (return (progn ,@body))))))

So your example would look like:

  (deftail doit-list (list collector)
    (if (atom list) collector
        (tail-call (doit-list (cdr list)
                              (cons (doit (car list))
                                     collector)))))


Using TAIL-CALL lets you separate out tail recursive calls to
DOIT-LIST from those that aren't.  Though i guess you could flag
norecursive calls instead, which might be more natural.

k