#'相等比较真实比较(列表7 1)但是与'(7 1)比较时为假,为什么?

时间:2016-01-19 13:36:37

标签: common-lisp sbcl

SBCL 1.3.1

总之,a是一个列表,'(7)b通过setq设置为相同的列表。值将附加到b。列表c设置为追加后的预期结果,即'(7 1)。然后将ac进行比较,并正确比较true。但是,当a通过(equal a '(7 1))进行比较时,会比较错误。

我的猜测是编译器没有看到追加,就像它在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)
* 

2 个答案:

答案 0 :(得分:5)

引用列表是&#34;文字&#34;,修改它们(就像在(setf (cdr b) ...)中那样)会导致不明确的后果。来自test-0的任何结果都是有效的,例如,格式化您的硬盘并炸掉您的房子。

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

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

答案 1 :(得分:4)

作为sds says,您正在修改文字数据,简短的回答是“不要这样做。”#34;修改文字数据是未定义的行为和&#34;任何&#34;可以发生。也就是说,结果通常是相对可预测的(例如,见Unexpected persistence of data),这种情况有点令人惊讶。似乎发生的事情是,当SBCL&#34;知道&#34;时,SBCL优化了呼叫(等于......)。这些值应该是不同的(因为它们是不同的文字)。

由于这是出乎意料的,我认为值得研究。我们可以将其分离为让 a c 成为文字列表,然后修改 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 的功能调用位置,但您没有看到相等的功能。我这里发生的是,当(7)时,编译器知道相等将返回false 7 1)进行比较,并硬编码。

测试此方法的一种方法可能是参数化测试,以便编译器无法对其进行优化。果然:

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

通过这种方式,您可以获得预期的结果。现在 a c 相等,但不是 eq

CL-USER> (maybe 'equal)
T

CL-USER> (maybe 'eq)
NIL

测试此方法的另一种方法是拨打副本列表并将 a 副本 c进行比较(假设SBCL无法优化对复制列表的调用,以生成 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)