有些 Lisp 对象永远不应该被修改。例如,Lisp 表达式 "aaa" 会生成一个字符串,但你不应该修改它的内容。还有一些对象本身就无法被修改;例如,虽然你可以通过计算得到一个新的数字,但 Lisp 不提供任何操作来修改一个已存在数字的值。
另一些 Lisp 对象是 mutable可变的:通过带有副作用的破坏性操作来修改它们的值是安全的。例如,一个已存在的标记(marker)可以通过移动到其他位置来被修改
虽然数字永远不可变、所有标记都是可变的,但某些类型既有可变成员,也有不可变成员。这些类型包括:点对(cons)、向量和字符串。例如:尽管 "cons" 和 (symbol-name 'cons) 都生成不应该被修改的字符串,但 (copy-sequence "cons") 和 (make-string 3 ?a) 都会生成可变字符串,后续可以通过 aset 来修改。
如果一个可变对象成为了被 eval 求值的表达式的一部分,它就不再是可变的。示例:
(let* ((x (list 0.5))
(y (eval (list 'quote x))))
(setcar x 1.5) ;; 程序不应该这么做
y)
虽然列表 (0.5) 在创建时是可变的,但因为它被传给了 eval,就不应该再用 setcar 修改。反向情况不会发生:本就不应该被修改的对象,之后永远不会变成可变对象。
如果程序试图修改不应该被修改的对象,其行为是未定义的:Lisp 解释器可能会抛出错误,也可能崩溃,或以其他不可预测的方式运行。3
当相似的常量作为程序的一部分出现时,Lisp 解释器可能会为了节省时间或空间,复用已有的常量或其组成部分。例如:(eq "abc" "abc") 在解释器只创建一个字符串字面量 "abc" 实例时返回 t,创建两个实例时返回 nil。编写 Lisp 程序时,应保证无论是否启用这类优化,程序都能正常运行。
这是 Common Lisp、C 等语言对常量规定的行为,这与 JavaScript、Python 等语言不同 —— 后者要求解释器在程序试图修改不可变对象时必须抛出错误。理想情况下,Emacs Lisp 解释器未来会向后者方向演进。