如何跳出Lisp中的函数?

时间:2014-02-26 10:45:51

标签: return lisp common-lisp tail-call-optimization trampolines

是否可以在(Common)Lisp中跳转到另一个函数而不是调用另一个函数? 我的意思是,当前函数被打破而另一个被调用,没有跳过数千个函数,好像我决定自己是否尾部调用优化,即使它不是尾部。 我不确定“(从fn x返回)”是否,我想要的。

示例:

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

'jump'应该像调用以下函数而不保存此函数的位置,而是返回原始funcall所在的位置,这样就不会有堆栈溢出。 只有当x为零时才应执行'rest'。

3 个答案:

答案 0 :(得分:5)

如果你需要一种尾部调用优化,比如结构中没有(必然)提供它的语言,但确实提供了闭包,你可以使用trampoline来实现持续的堆栈空间(关闭堆空间的权衡)当然是对象)。这与您要求的不完全相同,但您可能会发现它很有用。在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)))

要使用蹦床,只需使用执行计算第一部分的thunk来调用它。该闭包可以返回另一个thunk或结果。如果它返回thunk,那么因为它返回,所以回收初始堆栈帧,然后调用返回thunk的闭包。例如,这是非变量mapcar的实现可能是:

(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 '())))

当列表为空时,我们立即得到一个空列表:

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

如果不是,我们会回到thunk:

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

这意味着我们应该用trampoline包装一个调用(这也适用于基本情况,因为trampoline传递非thunk值):

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)

您的示例代码不足以成为一个说明性示例,但

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

将成为以下内容。 return提供了fn的提前终止,并且返回的thunk值提供了蹦床为您调用的“下一步”计算。

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

答案 1 :(得分:4)

你如何使用尾调用?

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

它在尾部位置调用fn。如果实现提供尾调用优化,则不会获得堆栈溢出。如果您不想依赖它,则需要以非递归方式处理问题。没有明确的&#39;删除此函数堆栈帧,然后调用函数X&#39; Common Lisp中的运算符。

答案 2 :(得分:0)

嗯,不是真的。我曾经尝试过

(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)))))))                

并且像scheme的let-let一样使用,例如:

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

但:

  1. 定义的对象(示例中为traverse)不是函数,即您不能funcallapply
  2. 这种构造并不能真正处理递归结构(即,由于不保留堆栈,因此不能使用它来遍历任意树而不是序列)
  3. 另一种方法可能是

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

    实际上解决了上面提到的问题,但是失去了“看马,没有堆栈空间”的财产。