3
votes

Is it possible in (Common) Lisp to jump to another function instead of call another? I mean, that the current function is broken and another is called, without jumping back through thousands of functions, as if I'd decide myself if tail call optimization is done, even if it is not the tail. I'm not sure if "(return-from fn x)" does, what I want.

Example:

(defun fn (x)
  (when x
    (princ x)
    (jump 'fn (cdr x)))
  (rest))

'jump' should be like calling the following function without saving the position of this function, instead returning to, where the original funcall was, so that there will be no stack overflow. 'rest' should only be executed if x is nil.

3
maybe you have some code which demonstrates what you want to do?Rainer Joswig
Note that to make this work, you still have to blow away the current stack frame, which is similar to what you'd have to do if you returned from the function normally. If you find a way to do this, you'll save on stack space, but you'll still have to do just the same number of stack frame setups and teardowns.Joshua Taylor
(return-from fn (fn (cdr x))) first calculates (fn (cdr x)) (and so causes recursion) and then returns its value as the result; what you want is to return first (control-wise), and then calculate the returned value (avoiding the recursive build-up of the stack). This answer shows how.Will Ness

3 Answers

5
votes

When you need a tail call optimization like structure in a language that doesn't (necessarily) provide it, but does provide closures, you can use a trampoline to achieve constant stack space (with a trade off for heap space for closure objects, of course). This isn't quite the same as what you asking for, but you might find it useful. It's pretty easy to implement in Common Lisp:

(defstruct thunk closure)

(defmacro thunk (&body body)
  `(make-thunk :closure (lambda () ,@body)))

(defun trampoline (thunk)
  (do ((thunk thunk (funcall (thunk-closure thunk))))
      ((not (thunk-p thunk)) thunk)))

To use the trampoline, you just call it with a thunk that performs the first part of your computation. That closure can either return another thunk, or a result. If it returns a thunk, then since it returned the initial stack frame is reclaimed, and then the closure of returned thunk is invoked. For instance, here's what an implementation of non-variadic mapcar might look like:

(defun t-mapcar1 (function list)
  (labels ((m (list acc)
             (if (endp list)
                 (nreverse acc)
                 (thunk 
                   (m (rest list)
                      (list* (funcall function (first list)) acc))))))
    (m list '())))

When the list is empty, we get an empty list immediately:

CL-USER> (t-mapcar1 '1+ '())
NIL

When it's not, we get back a thunk:

CL-USER> (t-mapcar1 '1+ '(1 2))
#S(THUNK :CLOSURE #<CLOSURE (LAMBDA #) {10033C7B39}>)

This means that we should wrap a call with trampoline (and this works fine for the base case too, since trampoline passes non-thunk values through):

CL-USER> (trampoline (t-mapcar1 '1+ '()))
NIL
CL-USER> (trampoline (t-mapcar1 '1+ '(1 2)))
(2 3)
CL-USER> (trampoline (t-mapcar1 '1+ '(1 2 3 4)))
(2 3 4 5)

Your example code isn't quite enough to be an illustrative example, but

(defun fn (x)
  (when x
    (princ x)
    (jump 'fn (cdr x)))
  (rest))

would become the following. The return provides the early termination from fn, and the thunk value that's returned provides the “next” computation that the trampoline would invoke for you.

(defun fn (x)
  (when x
    (princ x)
    (return (thunk (fn (cdr x)))))
  (rest))
4
votes

How about you use a tail call?

(defun fn (x)
  (if x
      (progn
        (princ x)
        (fn (cdr x)))
      (progn
        (rest))))

It calls fn in a tail position. If an implementation provides tail call optimization, you won't get a stack overflow. If you don't want to rely on that, you would need to handle the problem in a non recursive way. There are no explicit 'remove this functions stack frame and then call function X' operators in Common Lisp.

0
votes

Well, not really. I once did experiment with

(defmacro recurr (name bindings &body body)
  (let* ((names (mapcar #'car bindings))
         (restart (gensym "RESTART-"))
         (temp1 (gensym))
         (temp2 (gensym))
         (shadows (mapcar (lambda (name) (declare (ignore name)) (gensym)) names)))
    `(block ,name
       (let ,bindings
         (macrolet ((,name ,shadows
                      (list 'progn
                            (cons 'psetq
                                  (loop
                                    :for ,temp1 :in ',names
                                    :for ,temp2 :in (list ,@shadows)
                                    :nconcing (list ,temp1 ,temp2)))
                            (list 'go ',restart))))
           (tagbody
              ,restart
              (progn ,@body)))))))                

and to be used like scheme's named-let, e.g.:

(recurr traverse ((list '(1 2 3 4)))
   (if (null list) 'end 
       (traverse (cdr list))))

but:

  1. The object defined (traverse in the example) is not a function, i.e., you cannot funcall or apply it
  2. This kind of construct doesn't really cope with recursive structures (i.e., since no stack is kept, you cannot use it to traverse over arbitrary trees instead of sequences)

Another approach might be

(defmacro label (name (&rest bindings) &body body)
  `(labels ((,name ,(mapcar #'first bindings) ,@body))
     (,name ,@(mapcar #'second bindings))))

which actually addresses the points mentioned, but loses the "look ma, no stack space consing" property.