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

Why mutating the list to be only its first element with this approach does not work in Common Lisp?

我正在尝试通过 Common Lisp:对符号计算的简单介绍 来学习 Common Lisp。此外,我正在使用 SBCL、Emacs 和 Slime。

到第10章结束时,高级部分有这个问题:

10.9. Write a destructive function CHOP that shortens any non-NIL list to a list of one element. (CHOP '(FEE FIE FOE FUM)) should return (FEE).

这是答案-sheet解决方案:

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

如你所见,函数returns预期结果.

不幸的是,改变原始列表的副作用不会发生:

CL-USER> teste-chop
(A B C D)

为什么没变?

由于我将整个列表的字段(setf)设置为只有它的汽车包装为新列表,所以我期望原始列表的cdr消失。

显然,指针不会自动删除。

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

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

(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,即将列表 (a) 分配给这个局部变量。

因为这是函数的最后一个表达式,函数也返回这样的值,所以 (chop test-chop) returns 的计算值 (a).

在这个过程中,正如您所看到的,特殊变量 teste-chop 除了在函数调用评估开始时计算它的值外,没有任何关系。因此,它的值没有改变。

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

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

作为最后的评论,在 Common Lisp 中最好不要修改常量数据结构(如通过评估 '(a b c d) 获得的那些),因为它可能会产生未定义的行为,具体取决于实现。因此,如果某些结构应该是可修改的,则应该使用 conslist 等常用运算符构建它(例如 (list 'a 'b 'c 'd))。