Greetings, I feel timid sending this email to this list given that it’s been dormant since 2014. But after a few queries on #lisp, no better place was offered, and the currently-published information on CDRs directs readers to this list.
Here is the substance of my inquiry:
I believe CDR-8’s specification of EQUALS with respect to hash tables is incomplete, and its design may lead to some confusion. Here is an example, which uses an implementation of EQUALS provided by the library :generic-comparability[1]:
CL-USER> (ql:quickload :generic-comparability)
(:GENERIC-COMPARABILITY)
CL-USER> (defvar a (make-hash-table :test 'equalp))
A
CL-USER> (defvar b (make-hash-table :test 'equalp))
B
CL-USER> (setf (gethash "x" a) 1)
1
CL-USER> (setf (gethash "X" b) 1)
1
CL-USER> (loop for k being the hash-keys in a
always (eql (gethash k a) (gethash k b)))
T
CL-USER> (generic-comparability:equals a b)
NIL
Here we see EQUALS disagreeing with a GETHASH-based equality test[2], which CDR-8 might argue is by design. EQUALS is its own equality test, so it should override whatever is set in the hash table it’s given. However, the spec is not explicit on this point, which may be surprising to the unsuspecting programmer.
Moreover, let’s continue: let’s assume the :by-key argument to EQUALS is NIL, so the request is to compare only the hash table values. Well, the only way to correctly compare the values in two tables is to arrange them by their keys, and then compare them. To do otherwise would be meaningless. But what function should be used to arrange the keys? If EQUALS is used, then the result will be identical to :by-key T, and will be inconsistent with an EQUALP hash-table as per my example.
It would seem more appropriate to use the function designated by HASH-TABLE-TEST, but CDR-8 is silent on this question, which makes the specification incomplete.
A secondary problem with CDR-8 is in the example implementation given for hash tables. The LOOP presented implicitly depends on HT-KEYS returning keys in some sort of consistent, total ordering, because otherwise it would be meaningless to do a key-wise comparison. However, the spec is silent on this requirement. And it’s not obvious what comparison function CDR-8 would prefer: its own COMPARE, or the user-specified HASH-TABLE-TEST function.
In practice, at least under SBCL, iterating through a hash table with LOOP will generate the keys in a different order depending on their insertion order. (I believe the order in which hash table keys are iterated is unspecified in the standard.)
Furthermore, it seems nonsensical to provide for disjoint comparison of keys and values. For example, should a test of the form (EQUALS (:a 1 :b 2) (:a 2 :b 1) :by-keys T :by-value NIL) return T, and (... :by-keys NIL :by-value T) also return T?
The most common case for comparing hash tables is to ensure they have the same number of keys, that the set of keys in each table are equal, and that the values in the slots referred to by the keys are also equal. The proposed implementation does not provide this.
I hope this exposition is helpful. I’d accept any suggestions on how to help improve CDR-8 or :generic-comparability.
Best,
anticrisis
[1] https://github.com/pnathan/
[2] Looping through the keys of A is only part of the equality test. Not shown is checking the other hash table properties, in particular, the hash table test function, as returned by HASH-TABLE-TEST.