Common lisp 为什么用这种方法将列表变为它的第一个元素在CommonLisp中不起作用?

Common lisp 为什么用这种方法将列表变为它的第一个元素在CommonLisp中不起作用?,common-lisp,variable-assignment,in-place,mutability,Common Lisp,Variable Assignment,In Place,Mutability,我试图通过《CommonLisp:符号计算的温和介绍》一书来学习CommonLisp。此外,我正在使用SBCL、Emacs和Slime 在第10章末尾,关于高级部分,有一个问题: 10.9。写一个破坏性函数印章,将任何非零列表缩短为一个元素列表。(盖章)(费用为5美元)应退还 (费用) 这是答案表解决方案: (defun chop (x) (if (consp x) (setf (cdr x) nil)) x) 我理解这个解决方案。但是,在查看官方解决方案之前,我尝试了: (de

我试图通过《CommonLisp:符号计算的温和介绍》一书来学习CommonLisp。此外,我正在使用SBCL、Emacs和Slime

在第10章末尾,关于高级部分,有一个问题:

10.9。写一个破坏性函数印章,将任何非零列表缩短为一个元素列表。(盖章)(费用为5美元)应退还 (费用)

这是答案表解决方案:

(defun chop (x)
   (if (consp x) (setf (cdr x) nil)) 
   x)
我理解这个解决方案。但是,在查看官方解决方案之前,我尝试了:

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

显然,指针不会自动删除


因为我在低级的东西(比如指针)上有很大的差距,所以我认为这个问题的一些答案可以告诉我为什么会发生这种情况。

重点是关于如何在公共Lisp中向函数传递参数。它们是按值传递的。这意味着,当调用函数时,所有参数都将被求值,它们的值将被分配给新的局部变量,即函数的参数。所以,考虑一下你的功能:

(defun chop (xs)
  (cond ((null xs) xs)
        (t (setf xs (list (car xs))))))
当您使用以下命令调用它时:

(chop teste-chop)
teste chop
的值,即列表
(a b c d)
分配给功能参数
xs
。在函数体的最后一行,通过使用
setf
,您将一个新值
(list(car xs))
分配给
xs
,也就是说,您将list
(a)
分配给这个局部变量

由于这是函数的最后一个表达式,该函数也会返回该值,因此对
(chop test chop)
的求值将返回值
(a)

在这个过程中,您可以看到,除了在函数调用的求值开始时计算其值外,特殊变量
teste chop
与此无关。由于这个原因,它的值没有改变

其他语言中使用了其他形式的参数传递,例如按名称传递,因此函数调用的行为可能会有所不同

请注意,在第一个函数中,使用
(setf(cdr x)nil)
修改了数据结构,这是cons单元的一部分。由于全局变量绑定到该单元格,因此全局变量也将显示为已修改(即使在某种意义上它未被修改,因为它仍然绑定到同一单元格)


最后,在Common Lisp中,最好不要修改常量数据结构(如通过计算
”(a b c d)
)获得的数据结构),因为它可能会产生未定义的行为,具体取决于实现。因此,如果某些结构应该是可修改的,那么它应该使用常见的操作符来构建,如
cons
list
(例如
(list'a'b'c'd)
)。。。或者通常构建,这是最方便的,然后通过
复制列表
。setf是“set place”<代码>(setf(cdr-xs)…。设置(更改)位置
(cdr-xs)
(setf xs…
设置位置
xs
——这是函数的绑定
xs
,而不是它绑定到的值
setf
ing函数的形式参数永远不会影响该函数之外的任何内容。