为什么用这种方法将列表变异为它的第一个元素在CommonLisp中不起作用?
我正在尝试通过《Common Lisp:符号计算的温和介绍》一书来学习 Common Lisp 。此外,我正在使用 SBCL、Emacs 和 Slime。
到第 10 章结束时,在高级部分有这个问题:
10.9. 编写一个破坏性函数 CHOP,将任何非 NIL 列表缩短为一个元素的列表。(CHOP '(FEE FIE FOE FUM)) 应该返回 (FEE)。
这是答题纸的解决方案:
(defun chop (x)
(if (consp x) (setf (cdr x) nil))
x)
我理解这个解决方案。但是,在查看官方解决方案之前,我尝试过:
(defun chop (xs)
(cond ((null xs) xs)
(t (setf xs (list (car xs))))))
(defun chop (xs)
(cond ((null xs) xs)
(t (setf xs (list (car xs))))))
使用它作为测试的全局变量:
(defparameter teste-chop '(a b c d))
我试过 REPL:
CL-USER> (chop teste-chop)
(A)
如您所见,该函数返回预期结果。
不幸的是,不会发生改变原始列表的副作用:
CL-USER> teste-chop
(A B C D)
为什么它没有改变?
由于我将整个列表的字段 (setf) 设置为仅将其汽车包装为新列表,因此我希望原始列表的 cdr 消失。
显然,指针不会自动删除。
由于我在低级内容(例如指针)方面存在很大差距,因此我认为对这个问题的一些答案可以让我了解为什么会发生这种情况。
回答
重点是关于如何在 Common Lisp 中传递函数的参数。它们是按值传递的。这意味着,当一个函数被调用时,所有的参数都会被评估,并且它们的值被分配给new,局部变量,函数的参数。因此,请考虑您的功能:
当您使用以下命令调用它时:
(chop teste-chop)
该值的teste-chop,即列表(a b c d)分配给功能参数xs。在函数体的最后一行,通过使用setf,您将分配一个新值(list (car xs))to xs,即将列表分配(a)给此局部变量。
由于这是函数的最后一个表达式,因此该值也由函数返回,因此 的评估(chop test-chop)返回值(a)。
在这个过程中,如您所见,teste-chop除了在函数调用的评估开始时计算其值之外,没有任何方式关注特殊变量。因此它的值没有改变。
在其他语言中使用了其他形式的参数传递,例如通过 name,因此函数调用的行为可能会有所不同。
请注意,在第一个函数中,修改(setf (cdr x) nil)了数据结构,即 cons 单元的一部分。由于全局变量绑定到该单元格,因此全局变量也将显示为已修改(即使在某种意义上,它没有被修改,因为它仍然绑定到同一个cons 单元格)。
最后,在 Common Lisp 中,最好不要修改常量数据结构(如通过评估获得的数据结构'(a b c d)),因为它可能会产生未定义的行为,具体取决于实现。因此,如果某些结构应该是可修改的,则应该使用像consor list(eg (list 'a 'b 'c 'd))这样的常用运算符来构建它。