GNU bug report logs - #48425
Should #nil be equal? to '()?

Previous Next

Package: guile;

Reported by: Taylan Kammer <taylan.kammer <at> gmail.com>

Date: Fri, 14 May 2021 19:37:02 UTC

Severity: normal

Tags: patch

To reply to this bug, email your comments to 48425 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-guile <at> gnu.org:
bug#48425; Package guile. (Fri, 14 May 2021 19:37:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Taylan Kammer <taylan.kammer <at> gmail.com>:
New bug report received and forwarded. Copy sent to bug-guile <at> gnu.org. (Fri, 14 May 2021 19:37:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Taylan Kammer <taylan.kammer <at> gmail.com>
To: bug-guile <at> gnu.org
Subject: Should #nil be equal? to '()?
Date: Fri, 14 May 2021 21:36:39 +0200
[Message part 1 (text/plain, inline)]
I believe it would be better if #nil were equal? to ().

It would keep *not* being equal? to #f and as such not disturb the
property of transitiveness.

Making #nil and () be equal? would be a lot more intuitive since
they both represent the empty list, and since equal? is commonly
used to test the equality of lists.  Meeting this expectation would
probably prevent a common type of unexpected behavior where a list
coming from Elisp code is not equal? to a list coming from Scheme
code, even though they have the same contents.

Attached is a patch to realize the change.  Note that it increases
the size of compiled code that uses equal?.  I don't know if this
represents a problem or not.

Before patch:

scheme@(guile-user)> ,disassemble (lambda (x y) (equal? x y))
Disassembly of #<procedure 55dd585a0c58 at <unknown port>:1:13 (x y)> at #x55dd585a0ad4:

   0    (instrument-entry 131)                                at (unknown file):1:13
   2    (assert-nargs-ee/locals 3 0)    ;; 3 slots (2 args)
   3    (eq? 1 0)                                             at (unknown file):1:27
   4    (je 29)                         ;; -> L4
   5    (immediate-tag=? 1 7 0)         ;; heap-object?
   7    (jne 22)                        ;; -> L3
   8    (immediate-tag=? 0 7 0)         ;; heap-object?
  10    (jne 15)                        ;; -> L2
  11    (static-ref 2 96)               ;; #f
  13    (immediate-tag=? 2 7 0)         ;; heap-object?
  15    (je 7)                          ;; -> L1
  16    (call-scm<-scmn-scmn 2 103 107 113)
  20    (static-set! 2 87)              ;; #f
L1:
  22    (scm-ref/immediate 2 2 1)       
  23    (handle-interrupts)             
  24    (tail-call)                     
L2:
  25    (make-immediate 2 4)            ;; #f
  26    (reset-frame 1)                 ;; 1 slot
  27    (handle-interrupts)             
  28    (return-values)                 
L3:
  29    (make-immediate 2 4)            ;; #f
  30    (reset-frame 1)                 ;; 1 slot
  31    (handle-interrupts)             
  32    (return-values)                 
L4:
  33    (make-immediate 2 1028)         ;; #t
  34    (reset-frame 1)                 ;; 1 slot
  35    (handle-interrupts)             
  36    (return-values)                 

After patch:

scheme@(guile-user)> ,disassemble (lambda (x y) (equal? x y))
Disassembly of #<procedure 55b741d3ad50 at <unknown port>:8:13 (x y)> at #x55b741d3ab94:

   0    (instrument-entry 145)                                at (unknown file):8:13
   2    (assert-nargs-ee/locals 3 0)    ;; 3 slots (2 args)
   3    (eq? 1 0)                                             at (unknown file):8:27
   4    (je 43)                         ;; -> L6
   5    (immediate-tag=? 1 3583 260)    ;; null?
   7    (jne 12)                        ;; -> L2
   8    (immediate-tag=? 0 3583 260)    ;; null?
  10    (je 5)                          ;; -> L1
  11    (make-immediate 2 4)            ;; #f
  12    (reset-frame 1)                 ;; 1 slot
  13    (handle-interrupts)             
  14    (return-values)                 
L1:
  15    (make-immediate 2 1028)         ;; #t
  16    (reset-frame 1)                 ;; 1 slot
  17    (handle-interrupts)             
  18    (return-values)                 
L2:
  19    (immediate-tag=? 1 7 0)         ;; heap-object?
  21    (jne 22)                        ;; -> L5
  22    (immediate-tag=? 0 7 0)         ;; heap-object?
  24    (jne 15)                        ;; -> L4
  25    (static-ref 2 96)               ;; #f
  27    (immediate-tag=? 2 7 0)         ;; heap-object?
  29    (je 7)                          ;; -> L3
  30    (call-scm<-scmn-scmn 2 103 107 113)
  34    (static-set! 2 87)              ;; #f
L3:
  36    (scm-ref/immediate 2 2 1)       
  37    (handle-interrupts)             
  38    (tail-call)                     
L4:
  39    (make-immediate 2 4)            ;; #f
  40    (reset-frame 1)                 ;; 1 slot
  41    (handle-interrupts)             
  42    (return-values)                 
L5:
  43    (make-immediate 2 4)            ;; #f
  44    (reset-frame 1)                 ;; 1 slot
  45    (handle-interrupts)             
  46    (return-values)                 
L6:
  47    (make-immediate 2 1028)         ;; #t
  48    (reset-frame 1)                 ;; 1 slot
  49    (handle-interrupts)             
  50    (return-values)                 


- Taylan
[0001-Make-nil-and-equal-as-per-equal.patch (text/plain, attachment)]

Added tag(s) patch. Request was from Taylan Kammer <taylan.kammer <at> gmail.com> to control <at> debbugs.gnu.org. (Sat, 15 May 2021 17:35:02 GMT) Full text and rfc822 format available.

Information forwarded to bug-guile <at> gnu.org:
bug#48425; Package guile. (Thu, 24 Jun 2021 18:32:02 GMT) Full text and rfc822 format available.

Message #10 received at 48425 <at> debbugs.gnu.org (full text, mbox):

From: Leo Prikler <leo.prikler <at> student.tugraz.at>
To: Taylan Kammer <taylan.kammer <at> gmail.com>, 48425 <at> debbugs.gnu.org
Subject: Re: Should #nil be equal? to '()?
Date: Thu, 24 Jun 2021 20:31:12 +0200
Hi Taylan,

after a lengthy discussion in IRC, you invited me to reply to this bug
report, so that's what I'm doing.  I will make short references to that
discussion, but I won't rehash all of it.  Anyone who is interested in
reading it in all detail, should inspect the public logs:
  http://logs.guix.gnu.org/guile/2021-06-24.log#170309

Am Freitag, den 14.05.2021, 21:36 +0200 schrieb Taylan Kammer:
> I believe it would be better if #nil were equal? to ().
> 
> It would keep *not* being equal? to #f and as such not disturb the
> property of transitiveness.
I don't think there's a good reason to prefer one of this equal?ity
over the other – it all comes down to personal preference if at all.

> Making #nil and () be equal? would be a lot more intuitive since
> they both represent the empty list, and since equal? is commonly
> used to test the equality of lists.  Meeting this expectation would
> probably prevent a common type of unexpected behavior where a list
> coming from Elisp code is not equal? to a list coming from Scheme
> code, even though they have the same contents.
I don't think, that this is a good reason to make them equal?  We've
had our lengthy discussion on the philosophy of equality, which I'm
going to cut short here, as that's not the point you're making, but for
the protocol's sake, #nil and '() are not philosophically equal?

Regarding intuitiveness, I think this actually does more harm than
good.  I could argue that #nil be equal? to #f on the grounds of
intuitiveness, but more importantly

(if (equal? a b) (equal? (if a #t #f) (if b #t #f)))

should only ever return #t or *unspecified*, never #f (insert '() and
#nil as a and b).

Finally on the point of making equal?ity work that way for the sake of
interoperability with other Lisp dialects such as Emacs Lisp or
Clojure, this assumes that this style of comparison through equal? is
"good Scheme", when I'd argue, that it is in fact not.  Pattern
matching and dedicated comparison operators are much saner alternatives
most of the time.

There are multiple ways of getting around the issue of #nil, '() and #f
not being equal? to each other.  The first would be to define an equal-
modulo-nil? procedure, which returns #t if both arguments are nil? 
Another, that would implement your style, would be the following:

--8<---------------cut here---------------start------------->8---
;; Don't forget to test this when actually using it
(define my-equal?
  (match-lambda*
    ((() ()) #t) ;; match '() against #nil
    (((a . as) (b . bs))
     (and (my-equal? a b) ;; recurse into the first sub-element 
          (list= my-equal? as bs))) ;; recurse into the others
    ((a b) (equal? a b)))) ;; fall back to base equal?
--8<---------------cut here---------------end--------------->8---

Of course, you can also take the equal? you wrote for this patch and
include it in your own library, but be sure to document it so as to not
confuse the readers of your code :)

Note, that any such implementations will come with certain limitations,
but I suppose that's what you'd want out of them.

> Attached is a patch to realize the change.  Note that it increases
> the size of compiled code that uses equal?.  I don't know if this
> represents a problem or not.
I personally don't think bytecode optimizations are something to
consider if one can achieve correctness on the other hand, but this
solution has the downside of being both wrong (philosophically, as
discussed above and in IRC) and heavier to compute.  That's not
something a standard library should aim for :P

Regards,
Leo





This bug report was last modified 2 years and 306 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.