有人可以解释以下行为吗?具体来说,为什么该函数每次都返回不同的列表?为什么每次调用函数时
some-list
没有初始化为 '(0 0 0)
?
(defun foo ()
(let ((some-list '(0 0 0)))
(incf (car some-list))
some-list))
输出:
> (foo)
(1 0 0)
> (foo)
(2 0 0)
> (foo)
(3 0 0)
> (foo)
(4 0 0)
谢谢!
编辑:
另外,假设我希望该函数每次都输出
'(1 0 0)
,那么实现此函数的推荐方法是什么?
'(0 0 0)
是一个文字对象,它被假定为一个常量(尽管不受修改保护)。所以你每次都有效地修改同一个对象。要在每个函数调用时创建不同的对象,请使用 (list 0 0 0)
。
因此,除非您知道自己在做什么,否则应该始终仅使用文字列表(如
'(0 0 0)
)作为常量。
顺便说一句,在 sbcl REPL 中定义此函数,您会收到以下警告:
caught WARNING:
Destructive function SB-KERNEL:%RPLACA called on constant data.
See also:
The ANSI Standard, Special Operator QUOTE
The ANSI Standard, Section 3.2.2.3
这很好地提示了当前的问题。
源代码中的文字数据
代码中的'(0 0 0)
是文字数据。修改此数据具有未定义的行为。 Common Lisp 实现可能无法在运行时检测到它(除非数据被放置在某些只读内存空间中)。但它可能会产生不良影响。
您会发现这些数据可能(并且经常)在同一函数的不同调用之间共享
一个更微妙的可能错误是:Common Lisp 已经定义了各种优化,这些优化可以由编译器完成。例如,允许编译器重用数据:
示例:
(let ((a '(1 2 3))
(b '(1 2 3)))
(list a b))
在上面的代码片段中,编译器可能会检测到
a
和b
的文字数据是EQUAL
。然后,两个变量可能都指向相同的文字数据。修改它可能会起作用,但从 a
和 b
可以看到更改。
符号:是否是字面意思?
在 Common Lisp 中,带引号的数据符号实际上是一个文字对象。 即使是不带引号的数据表达式也是文字对象:例如,对于字符串,如果我们写
(quote "hello")
、'"hello"
或 `"hello",实际上没有什么区别。
CL-USER 5 > (defun foo () "Hello!")
FOO
CL-USER 6 > (eq (foo) (foo))
T
字符串表示法是文字对象的表示法。它不是字符串创建过程的简写符号。
总结: 文字数据的修改是几个细微错误的根源。如果可能的话避免它。然后你需要cons新的数据对象。 Consing 通常意味着在运行时分配新鲜的、新的数据结构。