On Lisp稍微早于 Common Lisp,因此存在一些不兼容性
On Lisp是在 Common Lisp 实际上被固化为一种语言之前编写的,因此出现在中的代码之间存在一些不兼容性On Lisp和 Common Lisp。这CLIki 条目On Lisp请注意,这些连续传递宏实际上是不兼容的地方之一(添加了强调):
当定义连续传递宏时(第 267 页)Paul Graham 似乎
假设cont全局变量具有词法范围。这
与 Common Lisp 标准相矛盾。与当今的 Common Lisp
实现中,上述宏不起作用。还有,这个
对于新手来说,这个问题可能会非常令人困惑。建议的解决方案
修复宏(请注意,使用 #'values 而不是
#'identity - 根据 Paul Graham 的勘误表):
- 模拟词法作用域的全局变量cont使用可以被 let 或 lambda 遮蔽的符号宏:
(定义变量实际续#'值)
(定义符号宏*续* *实际续*)
- 只需省略 (setq *cont* #'identity) 并将“顶级”连续传递函数调用为 (=somefunc #'values ...)
- …
这是对问题的简短描述,值得进一步研究,以帮助将来遇到该问题的新手。查看同一问题的其他讨论也可能会有所帮助,包括:
-
第268页...2006 年的 comp.lang.lisp 线程,其中用户询问以下两者之间的区别(设置*继续*...) and (defvar*续*...)。在这个帖子中,人们注意到On Lisp早于 ANSI Common Lisp。
到底发生了什么?
Since (=绑定...)扩展到(让((*续* …))…),你是绝对正确的,如果*cont*是一个特殊变量(即具有动态范围),那么一旦你超出了它let,原始绑定*cont*,即identity是调用重新启动时应该采取的措施。如果*cont*被声明为特殊,那么您会得到以下行为:
CONTINUATIONS> (=bind (node1) (dft-node '(a (b (d h)) (c e (f i) g)))
(if (eq node1 'done)
'done
(=bind (node2) (dft-node '(1 (2 (3 6 7) 4 5)))
(list node1 node2))))
(A 1)
CONTINUATIONS> (restart)
2
CONTINUATIONS> (restart)
3
CONTINUATIONS> (restart)
6
CONTINUATIONS> (restart)
7
CONTINUATIONS> (restart)
4
CONTINUATIONS> (restart)
5
CONTINUATIONS> (restart)
B
CONTINUATIONS> (restart)
D
这是有道理的,因为得到 (a 1) 后,*saved*包含两个函数。第一个是在下一个分支上继续遍历数字树,并且*cont*这将被称为全局的,#'身份,因为我们现在在=bind形式。这就是为什么我们得到 2、3……结果。第二个函数在*saved*此时将继续遍历 B 处的字母树。
上面我们没有得到一堆列表 (a 1), (a 2), …, (b 1) 等的原因是我们(合理地)假设*cont*是特殊的,即动态绑定的。事实证明,格雷厄姆的目的是*cont* not变得特别;他希望它成为一个全球词汇。从On Lisp,第 268 页:
这是通过操纵*cont*
我们会得到这样的效果
延续。虽然*cont*
具有全球价值,这很少会发生
使用的一个:*cont*
几乎总是一个参数,由=values
和定义的宏=defun
。体内add1
, 例如,*cont*
是一个参数而不是全局变量。这种区别很重要,因为这些
宏将不起作用,如果*cont*
不是局部变量。这就是为什么*cont*
其初始值在 a 中给出setq
代替defvar
:后者也会宣称它是特别的。
On Lisp稍微早于 Common Lisp,所以在撰写本文时这不一定是错误的,但 Common Lisp 实际上没有全局词法变量,所以情况并非如此(setq *继续* …)在顶层必然会创建一个全局词法变量。在 Common Lisp 中,确切的结果未指定。有些实现会将其视为全局词汇,其他实现会假设您的意思是定义参数 or defvar,你最终会得到一个全局的special多变的。正如格雷厄姆指出的那样,这是行不通的。听起来您已经有了执行后者的实现,所以事情不起作用。
有些实现实际上会抱怨他的代码。例如,SBCL 正确地抱怨(setq *cont* …)
,打印“警告:未定义的变量:CONTINUATIONS :: * CONT *”,并在以下情况下发出样式警告:*cont*它是“使用符号的词法绑定 (CONTINUATIONS::*CONT*),而不是动态绑定,即使名称遵循特殊变量的通常命名约定(如 *FOO* 之类的名称)”。
应该发生什么?
要了解这是怎么回事supposed为了工作,查看一个更简单的实现可能更容易,该实现没有所有的管道On Lisp版本:
(defparameter *restarts* '())
(defun do-restart ()
(if (endp *restarts*) nil
(funcall (pop *restarts*))))
(defun traverse-tree (k tree)
(cond
((null tree) (do-restart))
((atom tree) (funcall k tree))
(t (push (lambda () (traverse-tree k (cdr tree))) *restarts*)
(traverse-tree k (car tree)))))
这里我们不隐藏任何连续传递机制或*重新启动*列表。这样,我们得到了这样的行为:
CL-USER> (traverse-tree 'identity '((1 2) (3 4)))
1
CL-USER> (do-restart)
2
CL-USER> (do-restart)
3
CL-USER> (do-restart)
4
CL-USER> (do-restart)
NIL
我们也可以重新创建多列表遍历,我想我们得到了您期望的结果:
CL-USER> (let ((k (lambda (num)
(traverse-tree (lambda (alpha)
(list num alpha))
'(a (b) c)))))
(traverse-tree k '((1 2) 3)))
(1 A)
CL-USER> (do-restart)
(1 B)
CL-USER> (do-restart)
(1 C)
CL-USER> (do-restart)
(2 A)
CL-USER> (do-restart)
(2 B)
CL-USER> (do-restart)
(2 C)
CL-USER> (do-restart)
(3 A)
CL-USER> (do-restart)
(3 B)
CL-USER> (do-restart)
(3 C)
CL-USER> (do-restart)
NIL
这里的区别在于没有引用*cont*一旦我们离开范围,意义就会改变let这限制了我们的继续。
在我看来,更好的实现只需使用普通的词法 a 变量来存储延续(有点像k上面,但可能有一个由gensym),并且只要求“对连续传递函数的调用最终必须包装在=bind定义了最外层的延续。