是否可以在(Common)Lisp中跳转到另一个函数而不是调用另一个函数? 我的意思是,当前函数被打破而另一个被调用,没有跳过数千个函数,好像我决定自己是否尾部调用优化,即使它不是尾部。 我不确定“(从fn x返回)”是否,我想要的。
示例:
(defun fn (x)
(when x
(princ x)
(jump 'fn (cdr x)))
(rest))
'jump'应该像调用以下函数而不保存此函数的位置,而是返回原始funcall所在的位置,这样就不会有堆栈溢出。 只有当x为零时才应执行'rest'。
答案 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))))
但:
traverse
)不是函数,即您不能funcall
或apply
另一种方法可能是
(defmacro label (name (&rest bindings) &body body)
`(labels ((,name ,(mapcar #'first bindings) ,@body))
(,name ,@(mapcar #'second bindings))))
实际上解决了上面提到的问题,但是失去了“看马,没有堆栈空间”的财产。