John Fremlin's blog: Against equality in Lisp: eq, eql, equal and equalp

Posted 2009-02-19 23:00:00 GMT

The mess of options for testing whether objects are the same or similar or not in Common Lisp is an embarrassment. There have been some lame attempts to try to justify the proliferation of functions: eq, eql, equal, equalp by claiming there are many possible things one could mean by equality. Fair enough — or it would be if these different definitions were consistent and made any sense.

There are a lot of things that the Common Lisp standard really did well. For example, it's been possible to add Unicode to many Lisp implementations without hassle — compare that to the confusion Python suffered through. But the business of eq and friends is a shameful blot.

When you need to know whether two complex objects are the same, you are generally thinking of same as meaning either (1) the objects point to the same address in memory, or (2) have functionally identical values. In C++, which is frank and open about pointers, this is no problem — (1) (&a) == (&b) and (2) a == b. You can even overload == for your own classes.

Lisp is not frank and open about pointers because Lisp programmers are not to be trusted and have to be insulated from reality. In Lisp most objects are pointers already, but some aren't. If they are pointers then you can use eq for (1). That is, (eq a b). As some things are not pointers (for example, small numbers), there is a rather lame condition that (eq 1 1) can return true or false. But basically, (1) can be done.

There are three more standard equality functions: eql, equal, equalp. But still (2) is not part of the standard library!

eql is true if the objects are eq or they are both numbers or characters of the same type (floating point, character, integer, etc.) and are equal in value. This is useless except for numbers and (eql 0 0.0) is false, which means that (= 0 0.0) is much more useful (but that doesn't work on characters). This is another option for (1).

For a rough description of equal: it is true if eql is true. Otherwise: arrays that are either strings or bit-vectors are compared element by element with eql; and lists are compared element by element with equal. This doesn't really help and seems like it's leading us onto a giant pile of inconsistency.

equalp is our last hope for seeing if things are structurally similar. It starts off looking good, with numbers being equal if they are =, lets us compare user defined structure classes (even recursively), lists recursively, arrays recursively, and even hash tables. Everything is fine and dandy. Actually, no it isn't. I forgot to mention that equalp considers uppercase and lowercase to be the same. This means a massive performance hit with Unicode and an annoying punch in the face if you actually care about the case of strings.

Given the plethora of options for comparing equality the fact that we cannot really compare two objects for similarity of value unless WE CONSIDER THIS TO BE THE SAME as this, one has to think that something is WRONG.

Post a comment