2.9 可变性

一些 Lisp 对象永远不应该改变其内容。例如,Lisp 表达式"aaa"生成一个字符串,但您不应更改其内容。有些对象是不可变的;例如,尽管您可以通过计算 1 来创建一个新数字,但 Lisp 不提供更改现有数字值的操作。

其他 Lisp 对象是可变的:通过具有副作用的破坏性操作来更改它们的值是安全且被允许的。例如,可以通过将标记移动到其他位置来更改现有标记。

数字永远是不可变的,并且所有标记都是可变的。此外,某些类型具有若干成员,其中一些成员是可变的,而另一些则不是。这些类型包括点对、向量和字符串。例如,虽然"cons"(symbol-name 'cons)都生成不可变的字符串,但(copy-sequence "cons")(make-string 3 ?a)都生成可以通过 aset 改变的可变字符串。

如果可变对象是被求值的表达式的一部分,那它就不再是可变的。例如:

(let* ((x (list 0.5))
       (y (eval (list 'quote x))))
  (setcar x 1.5) ;; 程序不应该做这样的事情
  y)

尽管该列表(0.5)在创建时是可变的,但它不应通过 setcar 更改,因为它已提供给eval用于求值。 相反的情况则不会发生:不可变的对象在此之后永远不会变得可变。

如果程序试图更改不可变对象,则结果行为是未定义的:Lisp 解释器可能会抛出错误信号,或者它可能会崩溃或出现其他方式无法预测的反应。2

当类似的常量作为程序的一部分出现时,Lisp 解释器可能会通过重用现有的常量或其组件来节省时间或空间。例如,在例子(eq "abc" "abc")中,如果解释器只创建一个字符串常量的实例"abc"返回 t,如果它创建两个实例,则返回nil。应该编写这样的 Lisp 程序,以便无论是否使用这种优化,它们都可以工作。

脚注

(2)

这是为 Common Lisp 和 C 等语言指定的常量行为,这与 JavaScript 和 Python 等语言不同,如果程序试图更改不可变对象,则需要解释器来发出错误信号。理想情况下,Emacs Lisp 解释器将会在后续版本中覆盖这一问题。

最后更新于