在 Common Lisp 中映射数组时,两个缺点指向相同的内存

huy*_*yen 0 lisp arrays sbcl common-lisp cons

我有以下功能:

(defun transform-matrix (matrix)
   (let ((res (map 'vector
                   (lambda (x)
                       (map 'vector
                            (lambda (ix)
                                (if ix
                                    '(t 0) ; --> Problem happens here
                                    0))
                            x))
                   matrix)))
    res))
Run Code Online (Sandbox Code Playgroud)

此函数将接受一个二维矩阵,其中每个元素可以是 t 或 nil。然后它将转换 t -> '(t 0) 和 nil -> 0。

结果数组有一个问题是每个 (t 0) cons 现在都指向相同的内存位置。例如,如果我将结果数组保存在res变量中并执行以下操作:

(eq (aref (aref res 0) 0)
    (aref (aref res 1) 1))
Run Code Online (Sandbox Code Playgroud)

*假设 res[0][0] 和 res[1][1] 是 '(t, 0) 节点。

这将导致 T。但这样做是导致 nil:

(eq '(t 0) '(t 0))
Run Code Online (Sandbox Code Playgroud)

我能问一下使创建的 cons 指向相同内存位置的变换矩阵会发生什么吗?

我在 SBCL 2.0.0 Windows 64 位上测试这些代码。

谢谢你。

小智 8

查看此处问题的一种方法是将您的功能更改为:

(defun transform-matrix (matrix)
  (let ((non-nil-value '(t 0))
        (nil-value 0))
    (map 'vector
         (lambda (x)
           (map 'vector
                (lambda (ix)
                  (if ix non-nil-value nil-value))
                x))
         matrix)))
Run Code Online (Sandbox Code Playgroud)

应该清楚的是,这段代码在功能上是相同的:两个函数都只出现了一次'(t 0):这个只是给了它一个名字。

但是现在让我们分析这个函数并考虑一下:

(defun ts ()
  (let ((non-nil-value '(t 0)))
    (eq non-nil-value non-nil-value)))
Run Code Online (Sandbox Code Playgroud)

好吧,当然我希望调用这个函数的结果是t.

这就是为什么生成的嵌套向量中的每个元素都不0相同的原因:因为您只构造了一个对象。

如果希望返回值中的所有对象都是不同的对象(即不相同),则每次都需要构造一个新对象,例如:

(defun transform-matrix (matrix)
  (let ((non-nil-template '(t 0)))
    (map 'vector
         (lambda (x)
           (map 'vector
                (lambda (ix)
                  (if ix (copy-list non-nil-template) 0))
                x))
         matrix)))
Run Code Online (Sandbox Code Playgroud)

这将确保结果对象的每个非零元素

  • 是不同的;
  • 可以安全地变异。

这些以前都不是真的。

对于(eq '(t 0) '(t 0))您可能期望 this 必须返回的情况nil。这就是(我认为肯定)解释代码的情况。但是对于编译后的代码,答案并不是那么清楚。至少对于文件编译器来说,很明显实际上这可能会返回t. 规范的第 3.2.4.4 节部分

如果文件编译器处理的单个文件的源代码中出现的两个文字对象相同,则编译后的代码中对应的对象也必须相同。除了符号和包,文件编译器正在处理的代码中的任何两个文字对象都可以合并,当且仅当它们相似时;如果它们既是符号又是包,则仅当它们相同时才可以合并它们。

在 中(eq '(t 0) '(t 0)),有两个文字列表,它们是相似的,因此可以由文件编译器合并。

作为一般规则,如果你想要一个可变对象,或者你需要确定一个与任何其他对象不同的对象,你应该明确地构造它:改变文字对象永远是不安全的(甚至是合法的),并且可以合并对象的规则非常复杂,因此构造对象通常更安全,这样您就知道发生了什么。


顺便说一句,您使用嵌套向量而不是二维矩阵有什么原因吗?