6.3 Предикаты равенства

Common Lisp предоставляет ряд предикатов для проверки равенства двух объектов: eq (наиболее частный), eql, equal и equalp (наиболее общий). eq и equal имеют значения традиционные в Lisp’е. eql был добавлен, потому что он часто бывает необходим, и equalp был добавлен преимущественно, как версия equal, которая игнорирует различия типов при сравнении двух чисел и различия регистров при сравнении букв. Если два объекта удовлетворяют любому из этих предикатов, то они также удовлетворяют всем тем, которые носят более общий характер.

[Функция] eq x y

(eq x y) является истиной тогда и только тогда, когда, x и y являются идентичными объектами. (В реализациях, x и y обычно равны eq тогда и только тогда, когда обращаются к одной ячейке памяти.)

Необходимо отметить, что вещи, которые выводят одно и то же, необязательно равны eql друг другу. Символы с одинаковым именем обычно равны eq друг другу, потому что используется функция intern. Однако, одинаковые значения чисел могут быть не равны eq, и два похожих списка обычно не равны eq. Например:

(eq ’a ’b) ложь
(eq ’a ’a) истина
(eq 3 3) может быть истина или ложь, в зависимости от реализации
(eq 3 3.0) ложь
(eq 3.0 3.0) может быть истина или ложь, в зависимости от реализации
(eq #c(3 -4) #c(3 -4))
  может быть истина или ложь, в зависимости от реализации
(eq #c(3 -4.0) #c(3 -4)) ложь
(eq (cons ’a ’b) (cons ’a ’c)) ложь
(eq (cons ’a ’b) (cons ’a ’b)) ложь
(eq ’(a . b) ’(a . b)) может быть истина или ложь
(progn (setq x (cons ’a ’b)) (eq x x)) истина
(progn (setq x ’(a . b)) (eq x x)) истина
(eq #\A #\A) может быть истина или ложь, в зависимости от реализации
(eq "Foo" "Foo") может быть истина или ложь
(eq "Foo" (copy-seq "Foo")) ложь
(eq "FOO" "foo") ложь

В Common Lisp’е, в отличие от других диалектов, реализация в любое время может создавать «копии» букв и чисел. (Это сделано для возможности в повышении производительности.) Из этого следует правило, что Common Lisp не гарантирует для букв и чисел то, что eq будет истинен, когда оба аргумента являются «одним и тем же». Например:

(let ((x 5)) (eq x x)) может быть истиной или ложью

Предикат eql означает то же, что и eq, за исключением того, что если аргументы являются строковыми символами или числами одинакового типа, тогда сравниваются их значения. Таким образом eql говорит, являются ли два объекта «концептуально (conceptually)» одинаковыми, тогда как eq указывает, являются ли два объекта «реализационно (implementationally)» одинаковыми. По этой причине сравнительным предикатом для функций работы с последовательностями, описанными в главе 14, является eql, а не eq. _______________________________________________________________________

Заметка для реализации: eq simply compares the two given pointers, so any kind of object that is represented in an “immediate” fashion will indeed have like-valued instances satisfy eq. In some implementations, for example, fixnums and characters happen to “work.” However, no program should depend on this, as other implementations of Common Lisp might not use an immediate representation for these data types.

__________________________________________________________________________

An additional problem with eq is that the implementation is permitted to “collapse” constants (or portions thereof) appearing in code to be compiled if they are equal. An object is considered to be a constant in code to be compiled if it is a self-evaluating form or is contained in a quote form. This is why (eq "Foo" "Foo") might be true or false; in interpreted code it would normally be false, because reading in the form (eq "Foo" "Foo") would construct distinct strings for the two arguments to eq, but the compiler might choose to use the same identical string or two distinct copies as the two arguments in the call to eq. Similarly, (eq ’(a . b) ’(a . b)) might be true or false, depending on whether the constant conses appearing in the quote forms were collapsed by the compiler. However, (eq (cons ’a ’b) (cons ’a ’b)) is always false, because every distinct call to the cons function necessarily produces a new and distinct cons.

X3J13 voted in March 1989 to clarify that eval and compile are not permitted either to copy or to coalesce (“collapse”) constants (see eq) appearing in the code they process; the resulting program behavior must refer to objects that are eql to the corresponding objects in the source code. Only the compile-file/load process is permitted to copy or coalesce constants (see section 24.1).


[Функция] eql x y

Предикат eql истинен, если его аргументы равны eq, или если это числа одинакового типа и с одинаковыми значениями, или если это одинаковые буквы. Например:

(eql ’a ’b) ложь
(eql ’a ’a) истина
(eql 3 3) истина
(eql 3 3.0) ложь
(eql 3.0 3.0) истина
(eql #c(3 -4) #c(3 -4)) истина
(eql #c(3 -4.0) #c(3 -4)) ложь
(eql (cons ’a ’b) (cons ’a ’c)) ложь
(eql (cons ’a ’b) (cons ’a ’b)) ложь
(eql ’(a . b) ’(a . b)) может быть истиной или ложью
(progn (setq x (cons ’a ’b)) (eql x x)) истина
(progn (setq x ’(a . b)) (eql x x)) истина
(eql #\A #\A) истина
(eql "Foo" "Foo") может быть истиной или ложью
(eql "Foo" (copy-seq "Foo")) ложь
(eql "FOO" "foo") ложь

Обычно (eql 1.0s0 1.0d0) будет ложью, так как 1.0s0 и 1.0d0 не принадлежат одному типу данных. Однако в реализация может отсутствовать полный набор чисел с плавающей точкой, поэтому в такой ситуации (eql 1.0s0 1.0d0) может быть истиной. Предикат = будет сравнивать значения двух чисел, даже если числа принадлежат разным типам.

Если реализация поддерживает положительный и отрицательный нули, как различные значения (так IEEE стандарт предлагает реализовывать формат числа с плавающей точкой), тогда (eql 0.0 -0.0) будет ложью. В противном случае, когда синтаксис -0.0 интерпретируется, как значение 0.0, тогда (eql 0.0 -0.0) будет истиной. Предикат = отличается от eql в том, что (= 0.0 -0.0) будет всегда истинно, потому что = сравнивает математические значения операндов, тогда как eql сравнивает, так сказать, репрезентативные (representational) значения. FIXME.

Два комплексных числа будут равны eql, если их действительные части равны eql и мнимые части равны eql. Например, (eql #C(4 5) #C(4 5)) является истиной и (eql #C(4 5) #C(4.0 5.0)) является ложью. Следует отметить, что (eql #C(5.0 0.0) 5.0) ложь, а (eql #C(5 0) 5) истина. В случае с (eql #C(5.0 0.0) 5.0) два аргумента принадлежат разным типам и не равны eql, Однако, в случае (eql #C(5 0) 5), #C(5 0) не является комплексным числом, и автоматически преобразуется, по правилу канонизации комплексных чисел, в целое 5, так как дробное число 20/4 всегда упрощается до 5.

Случай (eql "Foo" "Foo") обсуждался выше в описании eq. Тогда как eql сравнивает значения чисел и букв, он не сравнивает содержимое строк. Сравнение символов двух строк может быть выполнено с помощью equal, equalp, string= или string-equal.


[Функция] equal x y

Предикат equal истинен, если его аргументы это структурно похожие (изоморфные) объекты. Грубое правило такое, что два объекта равны equal тогда и только тогда, когда одинаково их выводимое представление.

Числа и буквы сравниваются также как и в eql. Символы сравниваются как в eq. Этот метод сравнения символов может нарушать правило и сравнении выводимого представления, в случае если различия двух символов с одинаковым выводимым представлением.

Объекты, которые содержат другие элементы, будут равны equal, если они принадлежат одному типу и содержащиеся элементы равны equal. Эта проверка реализована в рекурсивном стиле и может быть зациклиться на закольцованных структурах.

Для cons-ячеек, equal определён рекурсивно, как сравнение equal сначала car элементов, а затем cdr.

Два массива равны equal только, если они равны eq, с одним исключением: строки и битовые вектора сравниваются поэлементно. Если какой-либо аргумент или оба содержат указатель заполнения (fill pointer), данный указатель ограничит количество проверяемых с помощью equal элементов. Буквы верхнего и нижнего регистров в строках расцениваются предикатом equal как разные. (А equalp игнорирует различие в регистрах в строках.)

Два объекта имени файла (pathname objects) равны equal тогда и только тогда, когда все элементы (хост, устройство, и т.д.) равны. (Будут ли равны буквы разных регистров зависит от файловой системы.) Имена файлов, которые равны equal, должны быть функционально эквивалентны.

equal рекурсивно рассматривает только следующие типы данных: cons-ячейки, битовые вектора, строки и имена файлов. Числа и буквы сравниваются так, как если бы сравнивались с помощью eql, а все остальные типы данных сравниваются как если бы с помощью eq.

(equal ’a ’b) ложь
(equal ’a ’a) истина
(equal 3 3) истина
(equal 3 3.0) ложь
(equal 3.0 3.0) истина
(equal #c(3 -4) #c(3 -4)) истина
(equal #c(3 -4.0) #c(3 -4)) ложь
(equal (cons ’a ’b) (cons ’a ’c)) ложь
(equal (cons ’a ’b) (cons ’a ’b)) истина
(equal ’(a . b) ’(a . b)) истина
(progn (setq x (cons ’a ’b)) (equal x x)) истина
(progn (setq x ’(a . b)) (equal x x)) истина
(equal #\A #\A) истина
(equal "Foo" "Foo") истина
(equal "Foo" (copy-seq "Foo")) истина
(equal "FOO" "foo") ложь

Для сравнения дерева cons-ячеек применяя eql (или любой другой желаемый предикат) для листьев, используйте tree-equal.


[Функция] equalp x y

Два объекта равны equalp, если они равны equal, если они буквы и удовлетворяют предикату char-equal, который игнорирует регистр и другие атрибуты символов, если они числа и имеют одинаковое значение, даже если числа разных типов, если они включает в себя элементы, которые также равны equalp.

Объекты, которые включают в себя элементы, равны equalp, если они принадлежат одному типу и содержащиеся элементы равны equalp. Проверка осуществляется в рекурсивном стиле и может не завершится на закольцованных структурах. Для cons-ячеек, предикат equalp определён рекурсивно и сравнивает сначала car элементы, а затем cdr.

Два массива равны equalp тогда и только тогда, когда они имеют одинаковое количество измерений, и размеры измерений совпадают, и все элементы равны equalp. Специализация массива не сравнивается. Например, строка и общий массив, случилось так, имеют одинаковые буквы, тогда они будут равны equalp (но определённо не равны equal). Если какой-либо аргумент содержит указатель заполнения, этот указатель ограничивает число сравниваемых элементов. Так как equalp сравнивает строки побуквенно, и не различает разных регистров букв, то сравнение строк регистронезависимо.

Два символа могут быть равны equalp только тогда, когда они eq, т.е. являются идентичными объектами.

X3J13 voted in June 1989 to specify that equalp compares components of hash tables (see below), and to clarify that otherwise equalp never recursively descends any structure or data type other than the ones explicitly described above: conses, arrays (including bit-vectors and strings), and pathnames. Numbers are compared for numerical equality (see =), characters are compared as if by char-equal, and all other data objects are compared as if by eq.

Two hash tables are considered the same by equalp if and only if they satisfy a four-part test:

The four parts of this test are carried out in the order shown, and if some part of the test fails, equalp returns nil and the other parts of the test are not attempted.

If equalp must compare two structures and the defstruct definition for one used the :type option and the other did not, then equalp returns nil.

If equalp must compare two structures and neither defstruct definition used the :type option, then equalp returns t if and only if the structures have the same type (that is, the same defstruct name) and the values of all corresponding slots (slots having the same name) are equalp.

As part of the X3J13 discussion of this issue the following observations were made. 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. Any decision to use or not use them should be determined by what they are documented to do rather than by any abstract characterization of their function. If neither equal nor equalp is found to be appropriate in a particular situation, programmers are encouraged to create another operator that is appropriate rather than blame equal or equalp for “doing the wrong thing.”

Note that one consequence of the vote to change the rules of floating-point contagion (described in section 12.1) is to make equalp a true equivalence relation on numbers.

(equalp ’a ’b) ложь
(equalp ’a ’a) истина
(equalp 3 3) истина
(equalp 3 3.0) истина
(equalp 3.0 3.0) истина
(equalp #c(3 -4) #c(3 -4)) истина
(equalp #c(3 -4.0) #c(3 -4)) истина
(equalp (cons ’a ’b) (cons ’a ’c)) ложь
(equalp (cons ’a ’b) (cons ’a ’b)) истина
(equalp ’(a . b) ’(a . b)) истина
(progn (setq x (cons ’a ’b)) (equalp x x)) истина
(progn (setq x ’(a . b)) (equalp x x)) истина
(equalp #\A #\A) истина
(equalp "Foo" "Foo") истина
(equalp "Foo" (copy-seq "Foo")) истина
(equalp "FOO" "foo") истина