将其翻译为Common Lisp

 望学有所得 发布于 2023-01-30 18:56

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

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

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

> ;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 个回答
  • 为什么代码有效

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

    > (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实现中您将获得相同的行为.请参阅意外的数据持久性和一些其他相关问题,以便讨论此处发生的情况.

    用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的答案,但实际上是一个&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

    2023-01-30 18:58 回答
  • Common Lisp:

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

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

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

    注意给不知情的读者:你可能想阅读奥林颤抖的纸张非常小心,尽量找出他实际上是指...

    2023-01-30 19:00 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有