#'equal compare true compared to (list 7 1) but false when compared to '(7 1),为什么?

#'equal compare true compared to (list 7 1) but false when compared to '(7 1), why?

SBCL 1.3.1

综上所述,a是一个列表,'(7)b通过setq设置为同一个列表。 b 后附加了一个值。列表 c 设置为附加后的预期结果,即 '(7 1)。然后将 ac 进行比较并正确地比较 true。但是,当通过 (equal a '(7 1)) 比较 a 时,它比较 false。

我的猜测是编译器没有看到追加,因为它是在 b 上完成的,并且优化了与不正确结果的常量比较。如果是这样,有哪些选项可以提示编译器。 a 能以某种方式被标记为特殊吗?或者,除了与破坏性编程相关的样式问题之外,这里还有其他问题吗?

    (defun test-0 ()
      (let ((a '(7))
            b
            (c '(7 1)))
        (setq b a)
        (setf (cdr b) (cons 1 '()))
        (pprint (list a b c))

        (values (equal c a) (equal '(7 1) a) (equal (list 7 1) a) c a)))


    * (test-0)

    ((7 1) (7 1) (7 1))
    T
    NIL  <== ??
    T
    (7 1)
    (7 1)

这是加载时的文字记录,运行 在空环境中。该文件是上述代码的复制和粘贴。可以看到没有错误消息。有趣的是在这里看到结果不同。

§sbcl> sbcl
This is SBCL 1.3.1.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (load "src/test-0")

T
* (test-0)

((7 1) (7 1) (7 1))
NIL
NIL
T
(7 1)
(7 1)
* 

引用的列表是 "literals",修改它们(就像您在 (setf (cdr b) ...) 中所做的那样)会导致未定义的后果。 test-0 的任何结果都是有效的,例如,格式化您的硬盘并炸毁您的房子。

'(7) 替换为 (list 7),您应该会得到您期望的结果。

PS。请正确格式化您的代码。

As , you're modifying literal data, and the short answer is "don't do that." Modifying literal data is undefined behavior and "anything" can happen. That said, usually the results are relatively predictable (e.g., see Unexpected persistence of data),这个案例有点令人惊讶。看起来发生的事情是 SBCL 优化了调用 (equal …) 当它 "knows" 值应该不同时(因为它们是不同的文字)。

由于这有点出乎意料,我认为值得研究。我们可以将其分离为让 ac 成为文字列表,然后修改 a 使它 等于 值:

CL-USER> (let* ((a '(7))
                (c '(7 1)))
           (rplacd a '(1))
           (equal a c))
; in: LET* ((A '(7)) (C '(7 1)))
;     (RPLACD A '(1))
; --> LET PROGN SETF 
; ==>
;   (SB-KERNEL:%RPLACD #:N-X0 '(1))
; 
; caught WARNING:
;   Destructive function SB-KERNEL:%RPLACD called on constant data.
;   See also:
;     The ANSI Standard, Special Operator QUOTE
;     The ANSI Standard, Section 3.2.2.3
; 
; compilation unit finished
;   caught 1 WARNING condition
NIL

现在让我们看一下表达式转换成的编译代码:

CL-USER> (disassemble (lambda ()
                        (let* ((a '(7))
                               (c '(7 1)))
                          (rplacd a '(1))
                          (equal a c))))

; disassembly for (LAMBDA ())
; Size: 60 bytes. Origin: #x10062874D4
; 4D4:       488D5C24F0       LEA RBX, [RSP-16]               ; no-arg-parsing entry point
; 4D9:       4883EC18         SUB RSP, 24
; 4DD:       488B158CFFFFFF   MOV RDX, [RIP-116]              ; '(7)
; 4E4:       488B3D8DFFFFFF   MOV RDI, [RIP-115]              ; '(1)
; 4EB:       488B058EFFFFFF   MOV RAX, [RIP-114]              ; #<FDEFINITION for SB-KERNEL:%RPLACD>
; 4F2:       B904000000       MOV ECX, 4
; 4F7:       48892B           MOV [RBX], RBP
; 4FA:       488BEB           MOV RBP, RBX
; 4FD:       FF5009           CALL QWORD PTR [RAX+9]
; 500:       BA17001020       MOV EDX, 537919511
; 505:       488BE5           MOV RSP, RBP
; 508:       F8               CLC
; 509:       5D               POP RBP
; 50A:       C3               RET
; 50B:       CC0A             BREAK 10                        ; error trap
; 50D:       02               BYTE #X02
; 50E:       19               BYTE #X19                       ; INVALID-ARG-COUNT-ERROR
; 50F:       9A               BYTE #X9A                       ; RCX

现在,您可以看到对 rplacd 的函数调用,但看不到 equal 的函数调用。我 认为 这里发生的事情是编译器知道 equal 将 return false 当 (7)(7 1) 进行比较,并对其进行硬编码。

测试这个的一种方法可能是参数化测试,这样编译器就无法优化它。果然:

(defun maybe (test)
  (let* ((a '(7))
         (c '(7 1)))
    (rplacd a '(1))
    (funcall test a c)))

这样,您将获得预期的结果。现在 ac 相等 ,但不是 eq :

CL-USER> (maybe 'equal)
T

CL-USER> (maybe 'eq)
NIL

另一种测试方法是调用 copy-list 并比较 copy of ac(假设 SBCL 不会优化对 copy-list 的调用以产生一个a 原始值的副本):

CL-USER> (let* ((a '(7))
                (c '(7 1)))
           (rplacd a '(1))
           (values (equal a c)
                   (equal (copy-list a) c)))
; ...
NIL    ; (equal a c)
T      ; (equal (copy-list a) c)