问题来源于PG的ANSI Common Lisp的第三章的练习6a, 大致意思是, car和cdr功能对调, 那么要你自己写一个cons函数.
当然, car和cdr的功能对调这一点我是忽略的, 我认为问题的重点在于自己实现一个cons函数. 想了一会, 做了一些测试之后, 想到了以下方法:
1 (defun qcons (x y) 2 (let ((tmp '(nil))) 3 (setf (car tmp) x 4 (cdr tmp) y) 5 tmp))
这个方法在简单的对atom操作时是没有问题的:
1 [32]> (qcons 'x nil) 2 (X)
但是当嵌套使用时, 程序却出现了卡住的情况, 内存猛涨, cpu占用飙高, 过了一会, repl提示"*** - No more room for LISP objects"(我用的是clisp), 去吃饭时想了一下, 估计是不知什么原因搞成circular list了, 打开*print-circle一看
1 [2]> (setf *print-circle* t) 2 T 3 [3]> (qcons 'x (qcons 'y 'z)) 4 #1=(X . #1#)
果然是这样, 开始以为是clisp的bug, 于是我又装了个sbcl来试了一下, 定义函数之后, sbcl提示我说对常量进行了修改之类的操作. 整个函数中只有tmp在初始化的时候赋值了常量, 修改qcons的代码:
1 (defun qcons (x y) 2 (let ((tmp (list nil))) 3 (setf (car tmp) x 4 (cdr tmp) y) 5 tmp))
问题解决.
对clisp和sbcl的内部实现不了解(sbcl也出现了这样的情况), 添加代码做了以下实验:
1 (defun qcons (x y) 2 (let ((tmp '(nil))) 3 (format t "tmp: ~A~%" tmp) 4 (setf (car tmp) x) 5 (setf (cdr tmp) y) 6 tmp))
执行之前的命令有以下结果:
1 [5]> (qcons 'x (qcons 'y 'z)) 2 tmp: (NIL) 3 tmp: (Y . Z) 4 #1=(X . #1#)
第三行输出表明, lisp中的let做了某些"优化", 对每个常量存放在一个固定的地址. 但是由于(qcons 'y 'z)修改了常量'(nil)的值, 并以其作为返回, 导致在第二次调用qcons时, tmp的值没有赋为'(nil), 而是eql于(qcons 'y 'z)的结果, 由此便出现了问题.
具体的东西由于对common lisp的了解还好少, 这里先做个记录, 提醒自己使用destructive函数时要避免常量.