Equality in Common Lisp - A cheat sheet
At work I program almost exclusively in Go (sometimes I need to step in and to some javascript, typscript and, shudders perl…). If I want to compare two values I simply pull out the trusty old ==
operator. Sure for slices and maps you can’t use that, but fortunately since 1.23 we can just use the slices.Equal
and maps.Equal
functions.
In go ==
is the catch-all (or catch-90%) when you need to know if two values are equal. It’s up to you to know what equality means for different types (and since it is go you will generally never be surprised on what that means).
In Common Lisp things are perhaps on the surface not as straight forward and I have found myself forgetting when to use which function (and what each equality function actually thinks about equality). This is post is meant to serve as a cheat sheet and reference for myself that I can easily look up.
In Common Lisp we get several flavours of equality and build up from the simplest form to more and more complex ones. To quote from the specification:
Object equality is not a concept for which there is a uniquely determined correct algorithm. The appropriateness of an equality predicate can be judged only in the context of the needs of some particular program. Although these functions take any type of argument and their names sound very generic, equal and equalp are not appropriate for every application.
EQ
eq
is the most basic equality function we have. According to the specification it “Returns true if its arguments are the same, identical object; otherwise, returns nil
.” To it, two things are equal if and only if they are the same object. For example (eq 'a 'a)
is t
but (eq 1 1.0)
will be nil
, we also have objects that can, depending on the implementation, be eq
. Take for example (eq 3 3)
or (eq "foo" "foo")
, depending on how the implementation you are running intern numbers and strings.
EQL
Everything that is eq
will also be eql
but provides some guarantees about numbers and characters. Whilst eq
could give use t
or nil
depending on the implementation when doing (eq 3 3)
, eql
guarantees it (the numbers have to be of the same type so (eql 3 3.0)
will still be nil
also note that (eql 3.31 3.31)
might not return t
, we will see the =
operator later on which is used for numbers). It also provides the same guarantee for characters.
eql
is the default equality predicate for most (but not all) operators.
EQUAL
In the spec we see that equal
: “Returns true if x and y are structurally similar (isomorphic) objects.”. What does that mean then?
Well again, objects that are eg
are also equal
, numbers and characters are equal
if they are eql
. That is equal
builds on eql
which in turn build on eq
. equal
also handles three other types of objects differently from eq
and eql
. For two conses x and y, equal
is defined recursively as:
(and (equal (car x) (car y))
(equal (cdr x) (cdr y)))
Strings and bit vectors are compared element-by-element with eql
(any other array is only equal
if they are eq
). equal
also handles pathnames, which are equal
if all the path components are the same.
=
=
is the function used for numbers and is t
if all numbers are the same value. Meaning (= 3 3.0)
is true.
char= and char-equal
char=
and char-equal
are quite straight forward. If the characters are the same char=
will be t
, char-equal
is the same but it is case insensitive.
string= and string-equal
Similarly string=
is t
if all of its characters are char=
and string-equal
if all of its characters are char-equal
.
equalp
We have now arrived at our final equality operator that we will talk about. The mighty and perhaps most complex, equalp
! I say mighty since it has a lot of rules to know.
The base case for equalp
is that two things are equalp
if they are equal
with the special rules for the following types:
Two characters are equalp
if they are char-equal
(i.e the same regardless of case). The same holds for strings, they are equalp
if all their characters are char-equal
. Numbers are equalp
if they are =
. Conses are defined in the same way as for equal
.
(and (equalp (car x) (car y))
(equalp (cdr x) (cdr y)))
Arrays are equalp
if their dimensions match and the elements are equalp
. Structures are equalp
if they are of the same class and their slots are equalp
. And finally, hash tables are equalp
if they have the same count as reported by the :test
function, the same keys as reported by the :test
function and the values matching the keys are equalp
.