Macros 将其转换为公共Lisp

Macros 将其转换为公共Lisp,macros,lisp,common-lisp,self-reference,Macros,Lisp,Common Lisp,Self Reference,我读了Olin Shivers的一篇文章,标题是,发现第二个例子(标记为“技术n-1”)有点令人费解。它描述了一个如下所示的自修改宏: (defun gen-counter macro (x) (let ((ans (cadr x))) (rplaca (cdr x) (+ 1 ans)) ans)) > ;this prints out the numbers from 0 to 9. (do ((n 0 (g

我读了Olin Shivers的一篇文章,标题是,发现第二个例子(标记为“技术n-1”)有点令人费解。它描述了一个如下所示的自修改宏:

(defun gen-counter macro (x)
       (let ((ans (cadr x)))   
     (rplaca (cdr x)       
         (+ 1 ans))
     ans))
> ;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
>
它的调用形式应该是参数
x
(即
(gen counter)
)。这样做的目的是能够做到以下几点:

(defun gen-counter macro (x)
       (let ((ans (cadr x)))   
     (rplaca (cdr x)       
         (+ 1 ans))
     ans))
> ;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中无效。我一直试图在Common Lisp中获得类似的行为,但没有成功。有人能提供一个CL中类似宏的工作示例吗

公共Lisp:

(defmacro gen-counter (&rest x)
  (let ((ans (car x)))   
    (rplaca x (+ 1 ans))
    ans))
但上述方法仅在解释器中有效,而不适用于编译器

对于编译后的代码,宏调用消失了——它被展开了——并且没有什么可修改的

请毫无戒心的读者注意:您可能想非常仔细地阅读Olin Shivers的论文,并试图找出他真正的意思……

为什么代码有效 首先,考虑本文第一个例子:

是有用的。
> (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)
它正在被修改。请注意,这实际上是公共Lisp中未定义的行为,但在一些公共Lisp实现中也会得到相同的行为。请参阅和其他一些相关问题,以讨论此处发生的情况

用公共Lisp逼近它 现在,您引用的论文和代码实际上对该代码的作用有一些有用的评论:

  (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
参数不同,实际上是一个
&total
参数,其中整个表单可以绑定到一个变量。这是使用程序的源代码作为被破坏性修改的文本值!现在,在本文中,此示例中使用了:

> ;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
然而,在CommonLisp中,我们希望宏只扩展一次。也就是说,我们希望
(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
当然,这是未定义的行为,因为它正在修改文字数据。它不能在所有的Lisp中工作(上面的运行来自CCL;它不能在SBCL中工作)

但不要错过重点 整篇文章有点有趣,但也要认识到这也是一个笑话。它指出,您可以在不编译代码的计算器中做一些有趣的事情。讽刺的是,它指出了Lisp系统的不一致性,这些系统在评估和编译时具有不同的行为。注意最后一段:

一些目光短浅的人会指出这些编程 技术,当然是值得称赞的,因为它们增加了清晰度和 效率,则编译代码会失败。不幸的是,这是事实。至少 上述两种技术将使大多数编译器进入无限状态 环但众所周知,大多数lisp编译器都不这样做 实现完整的lisp语义——例如,动态范围。这 这只是编译器未能保留语义的另一个例子 正确性。编译器实现者的任务仍然是 调整他的系统以正确实现源语言,而不是 比用户求助于丑陋、危险、不便携、不健壮 ``破解“”以围绕有缺陷的编译器进行编程

我希望这能让你对清洁、优雅的本质有所了解 Lisp编程技术

-奥林颤抖


(defmacro gen counter(&thill x num)(let((ans num))(rplaca(cdr x)(+1 ans))ans))
捕获表单…?无论如何,这并不是说它可以在我可以访问的任何Lisp中工作,但是…BRPocock:这很相似,但仍然只适用于解释器。在Olin Shivers的论文中,
x
实际上类似于
和整个x
;它被绑定到整个表单,因此源是正在使用(和修改)的文本数据。@JoshuaTaylor:对,但它只在解释器中起作用。&rest表单也有类似的效果。谢谢您详尽的回答。我以前没有实际使用过
&whole
说明符。CL对我来说仍然充满了惊喜:)。