将其翻译为Common Lisp

时间:2014-01-28 15:44:21

标签: macros lisp common-lisp self-reference

我一直在阅读Olin Shivers的一篇名为Stylish Lisp programming techniques的文章,并发现那里的第二个例子(标有“技术n-1”)有点令人费解。它描述了一个自修改宏,如下所示:

(defun gen-counter macro (x)
       (let ((ans (cadr x)))   
     (rplaca (cdr x)       
         (+ 1 ans))
     ans))

它应该将其调用形式作为参数x(即(gen-counter <some-number>))。这样做的目的是能够做到这样的事情:

> ;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
>

问题是在函数名称后面带有macro符号的语法在Common Lisp中无效。我试图在Common Lisp中获得类似的行为是不成功的。有人可以提供CL中类似宏的工作示例吗?

2 个答案:

答案 0 :(得分:6)

为什么代码有效

首先,考虑本文的第一个例子是有用的:

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

中逼近它

现在,你引用的论文和代码实际上对这段代码的作用有一些有用的评论:

  (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参数,整个表单可以绑定到变量。这是使用程序的作为破坏性修改的文字值!现在,在本文中,这个用于此示例:

> ;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语义 - 例如动态范围。这个   只是编译器无法保留语义的另一种情况   正确性。它仍然是编译器实现者的任务   调整他的系统以正确实现源语言   比用户诉诸丑陋,危险,非便携,非强健   “黑客”是为了围绕一个错误的编译器进行编程。

     

我希望这能提供一些清晰,优雅的本质   Lisp编程技术。

     

-Olin Shivers

答案 1 :(得分:5)

Common Lisp:

(defmacro gen-counter (&rest x)
  (let ((ans (car x)))   
    (rplaca x (+ 1 ans))
    ans))

但是上面只适用于解释器,而不适用于编译器。

使用已编译的代码,宏调用已经消失 - 它被扩展 - 并且没有任何内容可以修改。

请注意毫无戒心的读者:您可能需要仔细阅读Olin Shivers 非常的文章,并尝试找出他的实际含义......