为什么代码有效
首先,考虑论文中的第一个示例很有用:
> (defun element-generator ()
(let ((state '(() . (list of elements to be generated)))) ;() sentinel.
(let ((ans (cadr state))) ;pick off the first element
(rplacd state (cddr state)) ;smash the cons
ans)))
ELEMENT-GENERATOR
> (element-generator)
LIST
> (element-generator)
OF
> (element-generator)
这行得通,因为有 一个 文字列表
(() . (list of elements to be generated)
并且它正在被修改。请注意,这实际上是 Common Lisp 中未定义的行为,但在某些 Common Lisp 实现中您会得到相同的行为。请参阅Unexpected persistence of data 和其他一些相关问题,以讨论此处发生的情况。
在 Common Lisp 中逼近它
现在,你引用的论文和代码实际上有一些关于这段代码在做什么的有用的 cmets:
(defun gen-counter macro (x) ;X is the entire form (GEN-COUNTER n)
(let ((ans (cadr x))) ;pick the ans out of (gen-counter ans)
(rplaca (cdr x) ;increment the (gen-counter ans) form
(+ 1 ans))
ans)) ;return the answer
它的工作方式与&rest 参数不太像Rainer Joswig's answer,而是实际上是&whole 参数,其中整个表单可以绑定到一个变量。这是使用程序的 source 作为被破坏性修改的字面值!现在,在论文中,此示例中使用了它:
> ;this prints out the numbers from 0 to 9.
(do ((n 0 (gen-counter 1)))
((= n 10) t)
(princ n))
0.1.2.3.4.5.6.7.8.9.T
但是,在 Common Lisp 中,我们希望宏只被扩展一次。也就是说,我们希望(gen-counter 1) 被一些代码替换。不过,我们仍然可以生成一段这样的代码:
(defmacro make-counter (&whole form initial-value)
(declare (ignore initial-value))
(let ((text (gensym (string 'text-))))
`(let ((,text ',form))
(incf (second ,text)))))
CL-USER> (macroexpand '(make-counter 3))
(LET ((#:TEXT-1002 '(MAKE-COUNTER 3)))
(INCF (SECOND #:TEXT-1002)))
然后我们可以用do重新创建示例
CL-USER> (do ((n 0 (make-counter 1)))
((= n 10) t)
(princ n))
023456789
当然,这是未定义的行为,因为它正在修改文字数据。它不适用于所有 Lisps(上面的运行来自 CCL;它在 SBCL 中不起作用)。
但不要错过重点
整篇文章有点意思,但要知道这也是个笑话。它指出您可以在不编译代码的评估器中做一些有趣的事情。主要是讽刺指出 Lisp 系统在评估和编译时具有不同行为的不一致性。注意最后一段:
一些短视的人会指出,这些编程
技术,当然值得称赞的是它们提高了清晰度和
效率,编译代码会失败。可悲的是,这是真的。至少
上述两种技术将使大多数编译器进入无限
环形。但众所周知,大多数 lisp 编译器不会
实现完整的 lisp 语义——例如动态范围。这
只是编译器未能保留语义的另一种情况
正确性。 编译器实现者的任务仍然是
调整他的系统以正确实现源语言,而不是
比用户诉诸丑陋、危险、不便携、不健壮
``hacks'' 以便围绕有缺陷的编译器进行编程。
我希望这能让我们深入了解干净、优雅的本质
Lisp 编程技术。
——奥林颤抖