为什么用这种方法将列表改变为它的第一个元素在 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)
获得的那些),因为它可能会产生未定义的行为,具体取决于实现。因此,如果某些结构应该是可修改的,则应该使用 cons
或 list
等常用运算符构建它(例如 (list 'a 'b 'c 'd)
)。
我正在尝试通过 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)
获得的那些),因为它可能会产生未定义的行为,具体取决于实现。因此,如果某些结构应该是可修改的,则应该使用 cons
或 list
等常用运算符构建它(例如 (list 'a 'b 'c 'd)
)。