为什么这个函数每次都返回不同的值?

问题描述 投票:0回答:3

有人可以解释以下行为吗?具体来说,为什么该函数每次都返回不同的列表?为什么每次调用函数时

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)
,那么实现此函数的推荐方法是什么?

scope lisp common-lisp literals
3个回答
28
投票

'(0 0 0)
是一个文字对象,它被假定为一个常量(尽管不受修改保护)。所以你每次都有效地修改同一个对象。要在每个函数调用时创建不同的对象,请使用
(list 0 0 0)

因此,除非您知道自己在做什么,否则应该始终仅使用文字列表(如

'(0 0 0)
)作为常量。


12
投票

顺便说一句,在 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

这很好地提示了当前的问题。


6
投票

源代码中的文字数据

代码中的

'(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 通常意味着在运行时分配新鲜的、新的数据结构。

© www.soinside.com 2019 - 2024. All rights reserved.